gristlabs_grist-core/app/client/components/EmbedForm.js

206 lines
6.5 KiB
JavaScript
Raw Normal View History

// External dependencies
const _ = require('underscore');
const ko = require('knockout');
const BackboneEvents = require('backbone').Events;
// Grist client libs
const dispose = require('../lib/dispose');
const dom = require('../lib/dom');
const kd = require('../lib/koDom');
const kf = require('../lib/koForm');
const ModalDialog = require('./ModalDialog');
const gutil = require('app/common/gutil');
const BASE_URL = 'https://syvvdfor2a.execute-api.us-east-1.amazonaws.com/test';
/**
* EmbedForm - Handles logic and dom for the modal embedding instruction box.
*/
function EmbedForm(gristDoc) {
this._docComm = gristDoc.docComm;
this._login = gristDoc.app.login;
this._basketId = gristDoc.docInfo.basketId;
this._tableIds = gristDoc.docModel.allTableIds.peek().sort();
// Arrays of published and unpublished tables, initialized in this._refreshTables()
this._published = ko.observable([]);
this._unpublished = ko.observable([]);
// Notify strings which are displayed to the user when set
this._errorNotify = ko.observable();
this._updateNotify = ko.observable();
// The state of initialization, either 'connecting', 'failed', or 'done'.
this._initState = ko.observable('connecting');
this._embedDialog = this.autoDispose(ModalDialog.create({
title: 'Upload for External Embedding',
body: this._buildEmbedDom(),
width: '420px'
}));
this._embedDialog.show();
this.listenTo(this._embedDialog, 'close', () => this.dispose());
// Perform the initial fetch to see which tables are published.
this._initFetch();
}
_.extend(EmbedForm.prototype, BackboneEvents);
dispose.makeDisposable(EmbedForm);
/**
* Performs the initial fetch to see which tables are published.
* Times out after 4 seconds, giving the user the option to retry.
*/
EmbedForm.prototype._initFetch = function() {
this._initState('connecting');
return this._refreshTables()
.timeout(4000)
.then(() => {
this._initState('done');
})
.catch(err => {
console.error("EmbedForm._initFetch failed", err);
this._initState('failed');
});
};
/**
* Calls on basket to see which tables are published, then updates the published
* and unpublished local observables.
*/
EmbedForm.prototype._refreshTables = function() {
// Fetch the tables from the basket
return this._login.getBasketTables(this._docComm)
.then(basketTableIds => {
let published = [];
let unpublished = [];
gutil.sortedScan(this._tableIds, basketTableIds.sort(), (local, cloud) => {
let item = {
tableId: local || cloud,
local: Boolean(local),
cloud: Boolean(cloud)
};
if (cloud) {
published.push(item);
} else {
unpublished.push(item);
}
});
this._published(published);
this._unpublished(unpublished);
});
};
/**
* Builds the part of the form showing the table names and their status, and
* the buttons to change their status.
*/
EmbedForm.prototype._buildTablesDom = function() {
return dom('div.embed-form-tables',
kd.scope(this._published, published => {
return published.length > 0 ? dom('div.embed-form-published',
dom('div.embed-form-desc', `Published to Basket (basketId: ${this._basketId()})`),
published.map(t => {
return kf.row(
16, dom('a.embed-form-table-id', { href: this._getUrl(t.tableId), target: "_blank" },
t.tableId),
8, t.local ? this._makeButton('Update', t.tableId, 'update') : 'Only in Basket',
1, dom('div'),
2, this._makeButton('x', t.tableId, 'delete')
);
})
) : null;
}),
dom('div.embed-form-unpublished',
kd.scope(this._unpublished, unpublished => {
return unpublished.map(t => {
return kf.row(
16, dom('span.embed-form-table-id', t.tableId),
8, this._makeButton('Publish', t.tableId, 'add'),
3, dom('div')
);
});
})
)
);
};
/**
* Builds the body of the table publishing modal form.
*/
EmbedForm.prototype._buildEmbedDom = function() {
// TODO: Include links to the npm page and to download basket-api.js.
return dom('div.embed-form',
kd.scope(this._initState, state => {
switch (state) {
case 'connecting':
return dom('div.embed-form-connect', 'Connecting...');
case 'failed':
return dom('div',
dom('div.embed-form-connect', 'Connection to Basket failed'),
kf.buttonGroup(
kf.button(() => {
this._initFetch();
}, 'Retry')
)
);
case 'done':
return dom('div',
dom('div.embed-form-desc', 'Manage tables published to the cloud via Grist Basket.'),
dom('div.embed-form-desc', 'Note that by default, published tables are public.'),
this._buildTablesDom(),
dom('div.embed-form-desc', 'Basket is used to provide easy access to cloud-synced data:'),
dom('div.embed-form-link',
dom('a', { href: 'https://github.com/gristlabs/basket-api', target: "_blank" },
'Basket API on GitHub')
)
);
}
}),
kd.maybe(this._updateNotify, update => {
return dom('div.login-success-notify',
dom('div.login-success-text', update)
);
}),
kd.maybe(this._errorNotify, err => {
return dom('div.login-error-notify',
dom('div.login-error-text', err)
);
})
);
};
// Helper to perform embedAction ('add' | 'update' | 'delete') on tableId.
EmbedForm.prototype._embedTable = function(tableId, embedAction) {
this._errorNotify('');
this._updateNotify('');
return this._docComm.embedTable(tableId, embedAction)
.then(() => {
return this._refreshTables();
})
.then(() => {
if (embedAction === 'update') {
this._updateNotify(`Updated table ${tableId}`);
}
})
.catch(err => {
this._errorNotify(err.message);
});
};
// Helper to make a button with text, that when pressed performs embedAction
// ('add' | 'update' | 'delete') on tableId.
EmbedForm.prototype._makeButton = function(text, tableId, embedAction) {
return kf.buttonGroup(
kf.button(() => this._embedTable(tableId, embedAction), text)
);
};
// Returns the URL to see the hosted data for tableId.
EmbedForm.prototype._getUrl = function(tableId) {
return `${BASE_URL}/${this._basketId()}/tables/${tableId}`;
};
module.exports = EmbedForm;