diff --git a/README.md b/README.md
index faf84cd2be..717abd6dbc 100644
--- a/README.md
+++ b/README.md
@@ -399,6 +399,19 @@ For example:
You can conveniently create a filter definition without having to write it by hand by first saving a filter in the data browser, then exporting the filter definition under *App Settings > Export Class Preferences*.
+### Saving Configuration of the server
+
+You can save the confiugration on the server by specifying `preferencesClassName`
+```json
+"apps": [
+ {
+ "preferencesClassName": "DashboardPreferences"
+ }
+]
+```
+
+Once this is set, you can visit `AppSettings > Dashboard` to save the current Column and Class settings.
+
### Scripts
You can specify scripts to execute Cloud Functions with the `scripts` option:
diff --git a/jsconfig.json b/jsconfig.json
index 4a810717ee..17076526f2 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,6 +1,12 @@
{
"compilerOptions": {
- "experimentalDecorators": true
+ "experimentalDecorators": true,
+ "baseUrl": "src",
+ "paths": {
+ "lib/*": ["lib/*"],
+ "components/*": ["components/*"],
+ "stylesheets/*": ["stylesheets/*"]
+ }
},
"typeAcquisition": {
"include": [
diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js
index c61d216b29..68dd71980c 100644
--- a/src/dashboard/Data/Browser/Browser.react.js
+++ b/src/dashboard/Data/Browser/Browser.react.js
@@ -204,6 +204,11 @@ class Browser extends DashboardView {
this.action = new SidebarAction('Create a class', this.showCreateClass.bind(this));
}
+ if (this.context.preferencesClassName) {
+ ColumnPreferences.load(this.context.preferencesClassName);
+ ClassPreferences.load(this.context.preferencesClassName);
+ }
+
this.props.schema.dispatch(ActionTypes.FETCH).then(() => this.handleFetchedSchema());
if (!this.props.params.className && this.props.schema.data.get('classes')) {
this.redirectToFirstClass(this.props.schema.data.get('classes'));
diff --git a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
index 25b657b1e9..310e806930 100644
--- a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
+++ b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js
@@ -20,8 +20,11 @@ import * as ClassPreferences from 'lib/ClassPreferences';
import bcrypt from 'bcryptjs';
import * as OTPAuth from 'otpauth';
import QRCode from 'qrcode';
+import Parse from 'parse';
+import { CurrentApp } from 'context/currentApp';
export default class DashboardSettings extends DashboardView {
+ static contextType = CurrentApp;
constructor() {
super();
this.section = 'App Settings';
@@ -49,6 +52,7 @@ export default class DashboardSettings extends DashboardView {
show: false,
mfa: '',
},
+ showSavePreferences: !!this.context?.preferencesClassName,
};
}
@@ -63,6 +67,35 @@ export default class DashboardSettings extends DashboardView {
});
}
+ async saveColumns() {
+ const data = ColumnPreferences.getAllPreferences(this.context.applicationId);
+ let preferences = await new Parse.Query(this.context.preferencesClassName)
+ .equalTo('applicationId', this.context.applicationId)
+ .equalTo('key', 'columnPreferences')
+ .equalTo('user', Parse.User.current())
+ .first({ useMasterKey: true });
+
+ if (!preferences) {
+ preferences = new Parse.Object(this.context.preferencesClassName);
+ preferences.set('applicationId', this.context.applicationId);
+ preferences.set('key', 'columnPreferences');
+ preferences.set('user', Parse.User.current());
+ preferences.setACL(
+ new Parse.ACL(Parse.User.current())
+ );
+ }
+
+ preferences.set('value', JSON.stringify(data));
+
+ try {
+ await preferences.save(null, { useMasterKey: true });
+ this.showNote('Column preferences saved successfully');
+ } catch (error) {
+ this.showNote(`Error saving column preferences: ${error.message}`);
+ }
+
+ }
+
getClasses() {
const data = ClassPreferences.getAllPreferences(this.context.applicationId);
this.setState({
@@ -74,6 +107,31 @@ export default class DashboardSettings extends DashboardView {
});
}
+ async saveClasses() {
+ const data = ClassPreferences.getAllPreferences(this.context.applicationId);
+ let preferences = await new Parse.Query(this.context.preferencesClassName)
+ .equalTo('applicationId', this.context.applicationId)
+ .equalTo('key', 'classPreferences')
+ .equalTo('user', Parse.User.current())
+ .first({ useMasterKey: true });
+ if (!preferences) {
+ preferences = new Parse.Object(this.context.preferencesClassName);
+ preferences.set('applicationId', this.context.applicationId);
+ preferences.set('key', 'classPreferences');
+ preferences.set('user', Parse.User.current());
+ preferences.setACL(
+ new Parse.ACL(Parse.User.current())
+ );
+ }
+ preferences.set('value', JSON.stringify(data));
+ try {
+ await preferences.save(null, { useMasterKey: true });
+ this.showNote('Class preferences saved successfully');
+ } catch (error) {
+ this.showNote(`Error saving class preferences: ${error.message}`);
+ }
+ }
+
copy(data, label) {
navigator.clipboard.writeText(data);
this.showNote(`${label} copied to clipboard`);
@@ -367,10 +425,18 @@ export default class DashboardSettings extends DashboardView {
label={}
input={ this.getColumns()} />}
/>
+ {this.state.showSavePreferences && }
+ input={ this.saveColumns()} />}
+ />}
}
input={ this.getClasses()} />}
/>
+ {this.state.showSavePreferences && }
+ input={ this.saveClasses()} />}
+ />}
}
input={
diff --git a/src/lib/ClassPreferences.js b/src/lib/ClassPreferences.js
index 7e871de5e5..1d1cbf102f 100644
--- a/src/lib/ClassPreferences.js
+++ b/src/lib/ClassPreferences.js
@@ -1,4 +1,39 @@
const VERSION = 1; // In case we ever need to invalidate these
+import Parse from 'parse';
+
+export const load = async (preferencesClassName) => {
+ const preferences = await new Parse.Query(preferencesClassName)
+ .equalTo('user', Parse.User.current())
+ .equalTo('key', 'classPreferences')
+ .first({ useMasterKey: true });
+
+ if (preferences) {
+ const prefs = preferences.get('value');
+ setClassPreferences(JSON.parse(prefs), Parse.applicationId);
+ }
+
+}
+
+export function setClassPreferences(classPreference, appId) {
+ if (!classPreference) {
+ return;
+ }
+
+ for (const className in classPreference) {
+ const preferences = getPreferences(appId, className) || { filters: [] };
+ const { filters } = classPreference[className];
+ for (const filter of filters) {
+ if (Array.isArray(filter.filter)) {
+ filter.filter = JSON.stringify(filter.filter);
+ }
+ if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) {
+ continue;
+ }
+ preferences.filters.push(filter);
+ }
+ updatePreferences(preferences, appId, className);
+ }
+}
export function updatePreferences(prefs, appId, className) {
try {
localStorage.setItem(path(appId, className), JSON.stringify(prefs));
diff --git a/src/lib/ColumnPreferences.js b/src/lib/ColumnPreferences.js
index fb238837bc..3d355ec4e8 100644
--- a/src/lib/ColumnPreferences.js
+++ b/src/lib/ColumnPreferences.js
@@ -10,6 +10,24 @@ const DEFAULT_WIDTH = 150;
const COLUMN_SORT = '__columnClassesSort'; // Used for storing classes sort field
const DEFAULT_COLUMN_SORT = '-createdAt'; // Default column sorting
const cache = {};
+import Parse from 'parse';
+
+export const load = async (preferencesClassName) => {
+ const preferences = await new Parse.Query(preferencesClassName)
+ .equalTo('user', Parse.User.current())
+ .equalTo('key', 'columnPreferences')
+ .first({ useMasterKey: true });
+
+ if (preferences) {
+ const prefs = JSON.parse(preferences.get('value'));
+ if (prefs) {
+ for (const className in prefs) {
+ updatePreferences(prefs[className], Parse.applicationId, className);
+ }
+ }
+ }
+
+}
export function updatePreferences(prefs, appId, className) {
try {
diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js
index 18e4ab02bc..68881b3f05 100644
--- a/src/lib/ParseApp.js
+++ b/src/lib/ParseApp.js
@@ -8,7 +8,7 @@
import * as AJAX from 'lib/AJAX';
import encodeFormData from 'lib/encodeFormData';
import Parse from 'parse';
-import { updatePreferences, getPreferences } from 'lib/ClassPreferences';
+import { setClassPreferences } from 'lib/ClassPreferences';
function setEnablePushSource(setting, enable) {
const path = `/apps/${this.slug}/update_push_notifications`;
@@ -50,6 +50,7 @@ export default class ParseApp {
classPreference,
enableSecurityChecks,
cloudConfigHistoryLimit,
+ preferencesClassName
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
@@ -79,6 +80,7 @@ export default class ParseApp {
this.scripts = scripts;
this.enableSecurityChecks = !!enableSecurityChecks;
this.cloudConfigHistoryLimit = cloudConfigHistoryLimit;
+ this.preferencesClassName = preferencesClassName;
if (!supportedPushLocales) {
console.warn(
@@ -109,23 +111,7 @@ export default class ParseApp {
};
this.hasCheckedForMigraton = false;
-
- if (classPreference) {
- for (const className in classPreference) {
- const preferences = getPreferences(appId, className) || { filters: [] };
- const { filters } = classPreference[className];
- for (const filter of filters) {
- if (Array.isArray(filter.filter)) {
- filter.filter = JSON.stringify(filter.filter);
- }
- if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) {
- continue;
- }
- preferences.filters.push(filter);
- }
- updatePreferences(preferences, appId, className);
- }
- }
+ setClassPreferences(classPreference, appId);
}
setParseKeys() {