mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Some cleanup: remove old unused modules.
Summary: - Remove modules related to old login / profile that we don't plan to bring back. - Remove old unused DocListModel. - Remove ext* tests that have been skipped and don't work. - Remove old ModalDialog, and switch its one remaining usage to the newer way. Test Plan: All tests should pass, and as many as before. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2668
This commit is contained in:
		
							parent
							
								
									2e22966289
								
							
						
					
					
						commit
						f24a82e8d4
					
				@ -1,30 +0,0 @@
 | 
			
		||||
// Grist client libs
 | 
			
		||||
import * as ModalDialog from 'app/client/components/ModalDialog';
 | 
			
		||||
import * as dom from 'app/client/lib/dom';
 | 
			
		||||
import * as kd from 'app/client/lib/koDom';
 | 
			
		||||
import * as kf from 'app/client/lib/koForm';
 | 
			
		||||
 | 
			
		||||
export function showConfirmDialog(title: string, btnText: string, onConfirm: () => Promise<void>,
 | 
			
		||||
                                  explanation?: Element|string): void {
 | 
			
		||||
  const body = dom('div.confirm',
 | 
			
		||||
    explanation ? kf.row(explanation, kd.style('margin-bottom', '2rem')) : null,
 | 
			
		||||
    kf.row(
 | 
			
		||||
      1, kf.buttonGroup(
 | 
			
		||||
        kf.button(() => dialog.hide(), 'Cancel')
 | 
			
		||||
      ),
 | 
			
		||||
      1, kf.buttonGroup(
 | 
			
		||||
        kf.accentButton(async () => {
 | 
			
		||||
          await onConfirm();
 | 
			
		||||
          dialog.hide();
 | 
			
		||||
        }, btnText)
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
  const dialog = ModalDialog.create({
 | 
			
		||||
    title,
 | 
			
		||||
    body,
 | 
			
		||||
    width: '300px',
 | 
			
		||||
    show: true
 | 
			
		||||
  });
 | 
			
		||||
  dialog.once('close', () => dialog.dispose());
 | 
			
		||||
}
 | 
			
		||||
@ -1,205 +0,0 @@
 | 
			
		||||
// 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;
 | 
			
		||||
@ -1,129 +0,0 @@
 | 
			
		||||
/* global window */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// External dependencies
 | 
			
		||||
const Promise               = require('bluebird');
 | 
			
		||||
const ko                    = require('knockout');
 | 
			
		||||
 | 
			
		||||
// Grist client libs
 | 
			
		||||
const dispose               = require('../lib/dispose');
 | 
			
		||||
const ProfileForm           = require('./ProfileForm');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Login - Handles dom and settings for the login box.
 | 
			
		||||
 * @param {app} app - The app instance.
 | 
			
		||||
 */
 | 
			
		||||
function Login(app) {
 | 
			
		||||
  this.app = app;
 | 
			
		||||
  this.comm = this.app.comm;
 | 
			
		||||
 | 
			
		||||
  // When logged in, an object containing user profile properties.
 | 
			
		||||
  this._profile = ko.observable();
 | 
			
		||||
  this.isLoggedIn = ko.observable(false);
 | 
			
		||||
  this.emailObs = this.autoDispose(ko.computed(() => ((this._profile() && this._profile().email) || '')));
 | 
			
		||||
  this.nameObs = this.autoDispose(ko.computed(() => ((this._profile() && this._profile().name) || '')));
 | 
			
		||||
  this.buttonText = this.autoDispose(ko.computed(() =>
 | 
			
		||||
    this.isLoggedIn() ? this.emailObs() : 'Log in'));
 | 
			
		||||
 | 
			
		||||
  // Instantialized with createLoginForm() and createProfileForm()
 | 
			
		||||
  this.profileForm = null;
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(Login);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the current profile object.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.getProfile = function() {
 | 
			
		||||
  return this._profile();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Opens the Cognito login form in a new browser window to allow the user to log in.
 | 
			
		||||
 * The login tokens are sent back to the server to which this client belongs.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.login = function() {
 | 
			
		||||
  if (window.isRunningUnderElectron) {
 | 
			
		||||
    // Under electron, we open the login URL (it opens in a user's default browser).
 | 
			
		||||
    // With null for redirectUrl, it will close automatically when login completes.
 | 
			
		||||
    return this.comm.getLoginUrl(null)
 | 
			
		||||
    .then((loginUrl) => window.open(loginUrl));
 | 
			
		||||
  } else {
 | 
			
		||||
    // In hosted / dev version, we redirect to the login URL, and it will redirect back to the
 | 
			
		||||
    // starting URL when login completes.
 | 
			
		||||
    return this.comm.getLoginUrl(window.location.href)
 | 
			
		||||
    .then((loginUrl) => { window.location.href = loginUrl; });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tells the server to log out, and also opens a new window to the logout URL to get Cognito to
 | 
			
		||||
 * clear its cookies. The new window will hit our page which will close the window automatically.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.logout = function() {
 | 
			
		||||
  // We both log out the server, and for hosted version, visit a logout URL to clear AWS cookies.
 | 
			
		||||
  if (window.isRunningUnderElectron) {
 | 
			
		||||
    // Under electron, only clear the server state. Don't open the user's default browser
 | 
			
		||||
    // to clear cookies there because it serves dubious purpose and is annoying to the user.
 | 
			
		||||
    return this.comm.logout(null);
 | 
			
		||||
  } else {
 | 
			
		||||
    // In hosted / dev version, we redirect to the logout URL, which will clear cookies and
 | 
			
		||||
    // redirect back to the starting URL when logout completes.
 | 
			
		||||
    return this.comm.logout(window.location.href)
 | 
			
		||||
    .then((logoutUrl) => { window.location.href = logoutUrl; });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieves the updated user profile from DynamoDB and creates the profile form.
 | 
			
		||||
 * Also sends the fetched user profile to the server to keep it up to date.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.createProfileForm = function() {
 | 
			
		||||
  // ProfileForm disposes itself, no need to handle disposal.
 | 
			
		||||
  this.profileForm = ProfileForm.create(this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Called when the user logs out in this or another tab.
 | 
			
		||||
Login.prototype.onLogout = function() {
 | 
			
		||||
  this._profile(null);
 | 
			
		||||
  this.isLoggedIn(false);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Update the internally-stored profile given a profile object from the server.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.updateProfileFromServer = function(profileObj) {
 | 
			
		||||
  this._profile(profileObj);
 | 
			
		||||
  this.isLoggedIn(Boolean(this._profile.peek()));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Login.prototype.setProfileItem = function(key, val) {
 | 
			
		||||
  return this.comm.updateProfile({[key]: val});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an array of tableIds in the basket of the current document. If the current
 | 
			
		||||
 *  document has no basket, an empty array is returned.
 | 
			
		||||
 */
 | 
			
		||||
Login.prototype.getBasketTables = function(docComm) {
 | 
			
		||||
  return docComm.getBasketTables();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Execute func if the user is logged in. Otherwise, prompt the user to log in
 | 
			
		||||
//  and then execute the function. Attempts refresh if the token is expired.
 | 
			
		||||
Login.prototype.tryWithLogin = function(func) {
 | 
			
		||||
  return Promise.try(() => {
 | 
			
		||||
    if (!this.isLoggedIn()) {
 | 
			
		||||
      return this.login();
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  .then(() => func())
 | 
			
		||||
  .catch(err => {
 | 
			
		||||
    if (err.code === 'LoginClosedError') {
 | 
			
		||||
      console.log("Login#tryWithLogin", err);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw err;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = Login;
 | 
			
		||||
@ -1,125 +0,0 @@
 | 
			
		||||
/* global $, document, window */
 | 
			
		||||
 | 
			
		||||
var _ = require('underscore');
 | 
			
		||||
var ko = require('knockout');
 | 
			
		||||
var BackboneEvents = require('backbone').Events;
 | 
			
		||||
var dom = require('../lib/dom');
 | 
			
		||||
var kd = require('../lib/koDom');
 | 
			
		||||
var Base = require('./Base');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ModalDialog constructor creates a new ModalDialog element. The dialog accepts a bunch of
 | 
			
		||||
 * options, and the content can be overridde when calling .show(), so that a single ModalDialog
 | 
			
		||||
 * component can be used for different purposes. It triggers a 'close' event (using
 | 
			
		||||
 * Backbone.Events) when hidden.
 | 
			
		||||
 *
 | 
			
		||||
 * The DOM of the dialog is always attached to document.body.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Boolean} [options.fade]      Include `fade` css class to fade the modal in/out.
 | 
			
		||||
 * @param {Boolean} [options.close]     Include a close icon in the corner (default true).
 | 
			
		||||
 * @param {Boolean} [options.backdrop]  Include a modal-backdrop element (default true).
 | 
			
		||||
 * @param {Boolean} [options.keyboard]  Close the modal on Escape key (default true).
 | 
			
		||||
 * @param {Boolean} [options.show]      Shows the modal when initialized (default true).
 | 
			
		||||
 * @param {CSS String} [options.width]  Optional css width to override default.
 | 
			
		||||
 * @param {DOM|String} [options.title]  The content to place in the title.
 | 
			
		||||
 * @param {DOM|String} [options.body]   The content to place in the body.
 | 
			
		||||
 * @param {DOM|String} [options.footer] The content to place in the footer.
 | 
			
		||||
 */
 | 
			
		||||
function ModalDialog(options) {
 | 
			
		||||
  Base.call(this, options);
 | 
			
		||||
  options = options || {};
 | 
			
		||||
  this.options = _.defaults(options, {
 | 
			
		||||
    fade:     false,        // Controls whether the model pops or fades into view
 | 
			
		||||
    close:    true,        // Determines whether the "x" dismiss icon appears in the modal
 | 
			
		||||
    backdrop: true,
 | 
			
		||||
    keyboard: true,
 | 
			
		||||
    show:     false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // If the width option is set, the margins must be set to auto to keep the dialog centered.
 | 
			
		||||
  this.style = options.width ?
 | 
			
		||||
    `width: ${this.options.width}; margin-left: auto; margin-right: auto;` : '';
 | 
			
		||||
  this.title = ko.observable(options.title || null);
 | 
			
		||||
  this.body = ko.observable(options.body || null);
 | 
			
		||||
  this.footer = ko.observable(options.footer || null);
 | 
			
		||||
 | 
			
		||||
  this.modal = this.autoDispose(this._buildDom());
 | 
			
		||||
  document.body.appendChild(this.modal);
 | 
			
		||||
  $(this.modal).modal(_.pick(this.options, 'backdrop', 'keyboard', 'show'));
 | 
			
		||||
 | 
			
		||||
  // On applyState event, close the modal.
 | 
			
		||||
  this.onEvent(window, 'applyState', () => this.hide());
 | 
			
		||||
 | 
			
		||||
  // If disposed, let the underlying JQuery Modal run its hiding logic, and trigger 'close' event.
 | 
			
		||||
  this.autoDisposeCallback(this.hide);
 | 
			
		||||
}
 | 
			
		||||
Base.setBaseFor(ModalDialog);
 | 
			
		||||
_.extend(ModalDialog.prototype, BackboneEvents);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shows the ModalDialog. It accepts the same `title`, `body`, and `footer` options as the
 | 
			
		||||
 * constructor, which will replace previous content of those sections.
 | 
			
		||||
 */
 | 
			
		||||
ModalDialog.prototype.show = function(options) {
 | 
			
		||||
  options = options || {};
 | 
			
		||||
  // Allow options to specify new title, body, and footer content.
 | 
			
		||||
  ['title', 'body', 'footer'].forEach(function(prop) {
 | 
			
		||||
    if (options.hasOwnProperty(prop)) {
 | 
			
		||||
      this[prop](options[prop]);
 | 
			
		||||
    }
 | 
			
		||||
  }, this);
 | 
			
		||||
 | 
			
		||||
  $(this.modal).modal('show');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Hides the ModalDialog. This triggers the `close` to be triggered using Backbone.Events.
 | 
			
		||||
 */
 | 
			
		||||
ModalDialog.prototype.hide = function() {
 | 
			
		||||
  $(this.modal).modal('hide');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Internal helper to build the DOM of the dialog.
 | 
			
		||||
 */
 | 
			
		||||
ModalDialog.prototype._buildDom = function() {
 | 
			
		||||
  var self = this;
 | 
			
		||||
  // The .clipboard_focus class tells Clipboard.js to let this component have focus. Otherwise
 | 
			
		||||
  // it's impossible to select text.
 | 
			
		||||
  return dom('div.modal.clipboard_focus',
 | 
			
		||||
    { "role": "dialog", "tabIndex": -1 },
 | 
			
		||||
 | 
			
		||||
    // Emit a 'close' Backbone.Event whenever the dialog is hidden.
 | 
			
		||||
    dom.on('hidden.bs.modal', function() {
 | 
			
		||||
      self.trigger('close');
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    dom('div.modal-dialog', { style: this.style },
 | 
			
		||||
      dom('div.modal-content',
 | 
			
		||||
        kd.toggleClass('fade', self.options.fade),
 | 
			
		||||
 | 
			
		||||
        kd.maybe(this.title, function(title) {
 | 
			
		||||
          return dom('div.modal-header',
 | 
			
		||||
            kd.maybe(self.options.close, function () {
 | 
			
		||||
              return dom('button.close',
 | 
			
		||||
                {"data-dismiss": "modal", "aria-label": "Close"},
 | 
			
		||||
                dom('span', {"aria-hidden": true}, '×')
 | 
			
		||||
              );
 | 
			
		||||
            }),
 | 
			
		||||
            dom('h4.modal-title', title)
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        kd.maybe(this.body, function(body) {
 | 
			
		||||
          return dom('div.modal-body', body);
 | 
			
		||||
        }),
 | 
			
		||||
 | 
			
		||||
        kd.maybe(this.footer, function(footer) {
 | 
			
		||||
          return dom('div.modal-footer', footer);
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = ModalDialog;
 | 
			
		||||
@ -1,160 +0,0 @@
 | 
			
		||||
/* global document */
 | 
			
		||||
 | 
			
		||||
// 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 kf                    = require('../lib/koForm');
 | 
			
		||||
const kd                    = require('../lib/koDom');
 | 
			
		||||
const ModalDialog           = require('./ModalDialog');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ProfileForm - Handles dom and settings for the profile box.
 | 
			
		||||
 * @param {Login} login - The login instance.
 | 
			
		||||
 */
 | 
			
		||||
function ProfileForm(login) {
 | 
			
		||||
  this._login = login;
 | 
			
		||||
  this._comm = this._login.comm;
 | 
			
		||||
  this._gristLogin = this._login.gristLogin;
 | 
			
		||||
  this._errorNotify = ko.observable();
 | 
			
		||||
  this._successNotify = ko.observable();
 | 
			
		||||
 | 
			
		||||
  // Form data which may be filled in when modifying profile information.
 | 
			
		||||
  this._newName = ko.observable('');
 | 
			
		||||
 | 
			
		||||
  // Counter used to provide each edit profile sub-form with an id which indicates
 | 
			
		||||
  // when it is visible.
 | 
			
		||||
  this._formId = 1;
 | 
			
		||||
  this._editingId = ko.observable(null);
 | 
			
		||||
 | 
			
		||||
  this._profileDialog = this.autoDispose(ModalDialog.create({
 | 
			
		||||
    title: 'User profile',
 | 
			
		||||
    body: this._buildProfileDom(),
 | 
			
		||||
    width: '420px'
 | 
			
		||||
  }));
 | 
			
		||||
  this._profileDialog.show();
 | 
			
		||||
 | 
			
		||||
  // TODO: Some indication is necessary that verification is occurring between
 | 
			
		||||
  //  submitting the form and waiting for the box to close.
 | 
			
		||||
  this.listenTo(this._comm, 'clientLogout', () => this.dispose());
 | 
			
		||||
  this.listenTo(this._profileDialog, 'close', () => this.dispose());
 | 
			
		||||
}
 | 
			
		||||
_.extend(ProfileForm.prototype, BackboneEvents);
 | 
			
		||||
dispose.makeDisposable(ProfileForm);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Builds the body of the profile modal form.
 | 
			
		||||
 */
 | 
			
		||||
ProfileForm.prototype._buildProfileDom = function() {
 | 
			
		||||
  return dom('div.profile-form',
 | 
			
		||||
    // Email
 | 
			
		||||
    // TODO: Allow changing email
 | 
			
		||||
    this._buildProfileRow('Email', {
 | 
			
		||||
      buildDisplayFunc: () => dom('div',
 | 
			
		||||
        kd.text(this._login.emailObs),
 | 
			
		||||
        dom.testId('ProfileForm_viewEmail')
 | 
			
		||||
      )
 | 
			
		||||
    }),
 | 
			
		||||
    // Name
 | 
			
		||||
    this._buildProfileRow('Name', {
 | 
			
		||||
      buildDisplayFunc: () => dom('div',
 | 
			
		||||
        kd.text(this._login.nameObs),
 | 
			
		||||
        dom.testId('ProfileForm_viewName')
 | 
			
		||||
      ),
 | 
			
		||||
      buildEditFunc: () => dom('div',
 | 
			
		||||
        kf.label('New name'),
 | 
			
		||||
        kf.text(this._newName, {}, dom.testId('ProfileForm_newName'))
 | 
			
		||||
      ),
 | 
			
		||||
      submitFunc: () => this._submitNameChange()
 | 
			
		||||
    }),
 | 
			
		||||
    // TODO: Allow editing profile image.
 | 
			
		||||
    kd.maybe(this._successNotify, success => {
 | 
			
		||||
      return dom('div.login-success-notify',
 | 
			
		||||
        dom('div.login-success-text', success)
 | 
			
		||||
      );
 | 
			
		||||
    }),
 | 
			
		||||
    kd.maybe(this._errorNotify, err => {
 | 
			
		||||
      return dom('div.login-error-notify',
 | 
			
		||||
        dom('div.login-error-text', err)
 | 
			
		||||
      );
 | 
			
		||||
    })
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Builds a row of the profile form.
 | 
			
		||||
 * @param {String} label - Indicates the profile item displayed by the row.
 | 
			
		||||
 * @param {Function} options.buildDisplayFunc - A function which returns dom representing
 | 
			
		||||
 *  the value of the profile item to be displayed. If omitted, no value is visible.
 | 
			
		||||
 * @param {Function} options.buildEditFunc - A function which returns dom to change the
 | 
			
		||||
 *  value of the profile item. If omitted, the profile item may not be edited.
 | 
			
		||||
 * @param {Function} options.submitFunc - A function to call to save changes to the
 | 
			
		||||
 *  profile item. MUST be included if buildEditFunc is included.
 | 
			
		||||
 */
 | 
			
		||||
ProfileForm.prototype._buildProfileRow = function(label, options) {
 | 
			
		||||
  options = options || {};
 | 
			
		||||
  let formId = this._formId++;
 | 
			
		||||
 | 
			
		||||
  return dom('div.profile-row',
 | 
			
		||||
    kf.row(
 | 
			
		||||
      2, kf.label(label),
 | 
			
		||||
      5, options.buildDisplayFunc ? options.buildDisplayFunc() : '',
 | 
			
		||||
      1, dom('div.btn.edit-profile.glyphicon.glyphicon-pencil',
 | 
			
		||||
        { style: `visibility: ${options.buildEditFunc ? 'visible' : 'hidden'}` },
 | 
			
		||||
        dom.testId(`ProfileForm_edit${label}`),
 | 
			
		||||
        dom.on('click', () => {
 | 
			
		||||
          this._editingId(this._editingId() === formId ? null : formId);
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
    ),
 | 
			
		||||
    kd.maybe(() => this._editingId() === formId, () => {
 | 
			
		||||
      return dom('div',
 | 
			
		||||
        dom.on('keydown', e => {
 | 
			
		||||
          if (e.keyCode === 13) {
 | 
			
		||||
            // Current element is likely a knockout text field with changes that haven't yet been
 | 
			
		||||
            // saved to the observable. Blur the current element to ensure its value is saved.
 | 
			
		||||
            document.activeElement.blur();
 | 
			
		||||
            options.submitFunc();
 | 
			
		||||
          }
 | 
			
		||||
        }),
 | 
			
		||||
        dom('div.edit-profile-form',
 | 
			
		||||
          options.buildEditFunc(),
 | 
			
		||||
          dom('div.login-btns.flexhbox',
 | 
			
		||||
            kf.buttonGroup(
 | 
			
		||||
              kf.button(() => this._editingId(null), 'Cancel',
 | 
			
		||||
                dom.testId('ProfileForm_cancel'))
 | 
			
		||||
            ),
 | 
			
		||||
            kf.buttonGroup(
 | 
			
		||||
              kf.accentButton(() => options.submitFunc(), 'Submit',
 | 
			
		||||
                dom.testId('ProfileForm_submit'))
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
    })
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Submits the profile name change form.
 | 
			
		||||
ProfileForm.prototype._submitNameChange = function() {
 | 
			
		||||
  if (!this._newName()) {
 | 
			
		||||
    throw new Error('Name may not be blank.');
 | 
			
		||||
  }
 | 
			
		||||
  return this._login.setProfileItem('name', this._newName())
 | 
			
		||||
  // TODO: attemptRefreshToken() should be handled in a general way for all methods
 | 
			
		||||
  // which require using tokens after sign in.
 | 
			
		||||
  .then(() => {
 | 
			
		||||
    this._editingId(null);
 | 
			
		||||
    this._successNotify('Successfully changed name.');
 | 
			
		||||
  })
 | 
			
		||||
  .catch(err => {
 | 
			
		||||
    console.error('Error changing name', err);
 | 
			
		||||
    this._errorNotify(err.message);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = ProfileForm;
 | 
			
		||||
@ -5,7 +5,6 @@ var dom = require('../lib/dom');
 | 
			
		||||
var kd = require('../lib/koDom');
 | 
			
		||||
var kf = require('../lib/koForm');
 | 
			
		||||
var koArray = require('../lib/koArray');
 | 
			
		||||
var {showConfirmDialog} = require('./Confirm');
 | 
			
		||||
var SummaryConfig = require('./SummaryConfig');
 | 
			
		||||
var commands = require('./commands');
 | 
			
		||||
var {CustomSectionElement} = require('../lib/CustomSectionElement');
 | 
			
		||||
@ -20,6 +19,7 @@ const {basicButton, primaryButton} = require('app/client/ui2018/buttons');
 | 
			
		||||
const {colors} = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {cssDragger} = require('app/client/ui2018/draggableList');
 | 
			
		||||
const {menu, menuItem, select} = require('app/client/ui2018/menus');
 | 
			
		||||
const {confirmModal} = require('app/client/ui2018/modals');
 | 
			
		||||
const isEqual = require('lodash/isEqual');
 | 
			
		||||
const {cssMenuItem} = require('popweasel');
 | 
			
		||||
 | 
			
		||||
@ -421,18 +421,18 @@ ViewConfigTab.prototype._makeOnDemand = function(table) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (table.onDemand()) {
 | 
			
		||||
    showConfirmDialog('Unmark table On-Demand?', 'Unmark On-Demand', onConfirm,
 | 
			
		||||
    confirmModal('Unmark table On-Demand?', 'Unmark On-Demand', onConfirm,
 | 
			
		||||
      dom('div', 'If you unmark table ', dom('b', table), ' as On-Demand, ' +
 | 
			
		||||
        'its data will be loaded into the calculation engine and will be available ' +
 | 
			
		||||
        'for use in formulas. For a big table, this may greatly increase load times.',
 | 
			
		||||
        dom('br'), 'Changing this setting will reload the document for all users.')
 | 
			
		||||
        dom('br'), dom('br'), 'Changing this setting will reload the document for all users.')
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    showConfirmDialog('Make table On-Demand?', 'Make On-Demand', onConfirm,
 | 
			
		||||
    confirmModal('Make table On-Demand?', 'Make On-Demand', onConfirm,
 | 
			
		||||
      dom('div', 'If you make table ', dom('b', table), ' On-Demand, ' +
 | 
			
		||||
        'its data will no longer be loaded into the calculation engine and will not be available ' +
 | 
			
		||||
        'for use in formulas. It will remain available for viewing and editing.',
 | 
			
		||||
        dom('br'), 'Changing this setting will reload the document for all users.')
 | 
			
		||||
        dom('br'), dom('br'), 'Changing this setting will reload the document for all users.')
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								app/client/declarations.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								app/client/declarations.d.ts
									
									
									
									
										vendored
									
									
								
							@ -3,13 +3,10 @@ declare module "app/client/components/Clipboard";
 | 
			
		||||
declare module "app/client/components/CodeEditorPanel";
 | 
			
		||||
declare module "app/client/components/DetailView";
 | 
			
		||||
declare module "app/client/components/DocConfigTab";
 | 
			
		||||
declare module "app/client/components/EmbedForm";
 | 
			
		||||
declare module "app/client/components/FieldConfigTab";
 | 
			
		||||
declare module "app/client/components/GridView";
 | 
			
		||||
declare module "app/client/components/Layout";
 | 
			
		||||
declare module "app/client/components/LayoutEditor";
 | 
			
		||||
declare module "app/client/components/Login";
 | 
			
		||||
declare module "app/client/components/ModalDialog";
 | 
			
		||||
declare module "app/client/components/REPLTab";
 | 
			
		||||
declare module "app/client/components/commandList";
 | 
			
		||||
declare module "app/client/lib/Mousetrap";
 | 
			
		||||
@ -18,7 +15,6 @@ declare module "app/client/lib/dom";
 | 
			
		||||
declare module "app/client/lib/koDom";
 | 
			
		||||
declare module "app/client/lib/koForm";
 | 
			
		||||
declare module "app/client/lib/koSession";
 | 
			
		||||
declare module "app/client/models/DocListModel";
 | 
			
		||||
declare module "app/client/widgets/UserType";
 | 
			
		||||
declare module "app/client/widgets/UserTypeImpl";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,6 @@ Object.assign(window.exposedModules, {
 | 
			
		||||
  ko: require('knockout'),
 | 
			
		||||
  moment: require('moment-timezone'),
 | 
			
		||||
  Comm: require('./components/Comm'),
 | 
			
		||||
  ProfileForm: require('./components/ProfileForm'),
 | 
			
		||||
  _loadScript: require('./lib/loadScript'),
 | 
			
		||||
  ConnectState: require('./models/ConnectState'),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,118 +0,0 @@
 | 
			
		||||
var koArray = require('../lib/koArray');
 | 
			
		||||
var dispose = require('../lib/dispose');
 | 
			
		||||
var _ = require('underscore');
 | 
			
		||||
var BackboneEvents = require('backbone').Events;
 | 
			
		||||
var {pageHasDocList} = require('app/common/urlUtils');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constructor for DocListModel
 | 
			
		||||
 * @param {Object} comm: A map of server methods availble on this document.
 | 
			
		||||
 */
 | 
			
		||||
function DocListModel(app) {
 | 
			
		||||
  this.app = app;
 | 
			
		||||
  this.comm = this.app.comm;
 | 
			
		||||
  this.docs = koArray();
 | 
			
		||||
  this.docInvites = koArray();
 | 
			
		||||
 | 
			
		||||
  if (pageHasDocList()) {
 | 
			
		||||
    this.listenTo(this.comm, 'docListAction', this.docListActionHandler);
 | 
			
		||||
    this.listenTo(this.comm, 'receiveInvites', () => this.refreshDocList());
 | 
			
		||||
 | 
			
		||||
    // Initialize the DocListModel
 | 
			
		||||
    this.refreshDocList();
 | 
			
		||||
  } else {
 | 
			
		||||
    console.log("Page has no DocList support");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(DocListModel);
 | 
			
		||||
_.extend(DocListModel.prototype, BackboneEvents);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rebuilds DocListModel with a direct call to the server.
 | 
			
		||||
 */
 | 
			
		||||
DocListModel.prototype.refreshDocList = function() {
 | 
			
		||||
  return this.comm.getDocList()
 | 
			
		||||
    .then(docListObj => {
 | 
			
		||||
      this.docs.assign(docListObj.docs);
 | 
			
		||||
      this.docInvites.assign(docListObj.docInvites);
 | 
			
		||||
    })
 | 
			
		||||
    .catch((err) => {
 | 
			
		||||
      console.error('Failed to load DocListModel: %s', err);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates the DocListModel docs and docInvites arrays in response to docListAction events
 | 
			
		||||
 * @param {Object} message: A docListAction message received from the server.
 | 
			
		||||
 */
 | 
			
		||||
DocListModel.prototype.docListActionHandler = function(message) {
 | 
			
		||||
  console.log('docListActionHandler message', message);
 | 
			
		||||
  if (message && message.data) {
 | 
			
		||||
    _.each(message.data.addDocs, this.addDoc, this);
 | 
			
		||||
    _.each(message.data.removeDocs, this.removeDoc, this);
 | 
			
		||||
    _.each(message.data.changeDocs, this.changeDoc, this);
 | 
			
		||||
    _.each(message.data.addInvites, this.addInvite, this);
 | 
			
		||||
    _.each(message.data.removeInvites, this.removeInvite, this);
 | 
			
		||||
    // DocListModel can ignore rename events since renames also broadcast add/remove events.
 | 
			
		||||
  } else {
 | 
			
		||||
    console.error('Unrecognized message', message);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype._removeAtIndex = function(collection, index) {
 | 
			
		||||
  collection.splice(index, 1);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype._removeItem = function(collection, name) {
 | 
			
		||||
  var index = this._findItem(collection, name);
 | 
			
		||||
  if (index !== -1) {
 | 
			
		||||
    this._removeAtIndex(collection, index);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Binary search is disabled in _.indexOf because the docs may not be sorted by name.
 | 
			
		||||
DocListModel.prototype._findItem = function(collection, name) {
 | 
			
		||||
  var matchIndex = _.indexOf(collection.all().map(item => item.name), name, false);
 | 
			
		||||
  if (matchIndex === -1) {
 | 
			
		||||
    console.error('DocListModel does not contain name:', name);
 | 
			
		||||
  }
 | 
			
		||||
  return matchIndex;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype.removeDoc = function(name) {
 | 
			
		||||
  this._removeItem(this.docs, name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: removeInvite is unused
 | 
			
		||||
DocListModel.prototype.removeInvite = function(name) {
 | 
			
		||||
  this._removeItem(this.docInvites, name);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype._addItem = function(collection, fileObj) {
 | 
			
		||||
  var insertIndex = _.sortedIndex(collection.all(), fileObj, 'name');
 | 
			
		||||
  this._addItemAtIndex(collection, insertIndex, fileObj);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype._addItemAtIndex = function(collection, index, fileObj) {
 | 
			
		||||
  collection.splice(index, 0, fileObj);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DocListModel.prototype.addDoc = function(fileObj) {
 | 
			
		||||
  this._addItem(this.docs, fileObj);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: addInvite is unused
 | 
			
		||||
DocListModel.prototype.addInvite = function(fileObj) {
 | 
			
		||||
  this._addItem(this.docInvites, fileObj);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Called when the metadata for a doc changes.
 | 
			
		||||
DocListModel.prototype.changeDoc = function(fileObj) {
 | 
			
		||||
  let idx = this._findItem(this.docs, fileObj.name);
 | 
			
		||||
  if (idx !== -1) {
 | 
			
		||||
    this._removeAtIndex(this.docs, idx);
 | 
			
		||||
    this._addItem(this.docs, fileObj);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = DocListModel;
 | 
			
		||||
@ -3,13 +3,11 @@ import * as Clipboard from 'app/client/components/Clipboard';
 | 
			
		||||
import {Comm} from 'app/client/components/Comm';
 | 
			
		||||
import * as commandList from 'app/client/components/commandList';
 | 
			
		||||
import * as commands from 'app/client/components/commands';
 | 
			
		||||
import * as Login from 'app/client/components/Login';
 | 
			
		||||
import {unsavedChanges} from 'app/client/components/UnsavedChanges';
 | 
			
		||||
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
 | 
			
		||||
import {isDesktop} from 'app/client/lib/browserInfo';
 | 
			
		||||
import * as koUtil from 'app/client/lib/koUtil';
 | 
			
		||||
import {reportError, TopAppModel, TopAppModelImpl} from 'app/client/models/AppModel';
 | 
			
		||||
import * as DocListModel from 'app/client/models/DocListModel';
 | 
			
		||||
import {setUpErrorHandling} from 'app/client/models/errors';
 | 
			
		||||
import {createAppUI} from 'app/client/ui/AppUI';
 | 
			
		||||
import {attachCssRootVars} from 'app/client/ui2018/cssVars';
 | 
			
		||||
@ -24,9 +22,6 @@ import * as ko from 'knockout';
 | 
			
		||||
 | 
			
		||||
const G = getBrowserGlobals('document', 'window');
 | 
			
		||||
 | 
			
		||||
type DocListModel = any;
 | 
			
		||||
type Login = any;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Main Grist App UI component.
 | 
			
		||||
 */
 | 
			
		||||
@ -40,9 +35,7 @@ export class App extends DisposableWithEvents {
 | 
			
		||||
  public comm = this.autoDispose(Comm.create());
 | 
			
		||||
  public clientScope: ClientScope;
 | 
			
		||||
  public features: ko.Computed<ISupportedFeatures>;
 | 
			
		||||
  public login: Login;
 | 
			
		||||
  public topAppModel: TopAppModel;    // Exposed because used by test/nbrowser/gristUtils.
 | 
			
		||||
  public docListModel: DocListModel;
 | 
			
		||||
 | 
			
		||||
  private _settings: ko.Observable<{features?: ISupportedFeatures}>;
 | 
			
		||||
 | 
			
		||||
@ -64,16 +57,11 @@ export class App extends DisposableWithEvents {
 | 
			
		||||
    this._settings = ko.observable({});
 | 
			
		||||
    this.features = ko.computed(() => this._settings().features || {});
 | 
			
		||||
 | 
			
		||||
    // Creates a Login instance which handles building the login form, login/signup, logout,
 | 
			
		||||
    // and refreshing tokens. Uses .features, so instantiated after that.
 | 
			
		||||
    this.login = this.autoDispose(Login.create(this));
 | 
			
		||||
 | 
			
		||||
    if (isDesktop()) {
 | 
			
		||||
      this.autoDispose(Clipboard.create(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.topAppModel = this.autoDispose(TopAppModelImpl.create(null, G.window));
 | 
			
		||||
    this.docListModel = this.autoDispose(DocListModel.create(this));
 | 
			
		||||
 | 
			
		||||
    const isHelpPaneVisible = ko.observable(false);
 | 
			
		||||
 | 
			
		||||
@ -125,7 +113,6 @@ export class App extends DisposableWithEvents {
 | 
			
		||||
    this.listenTo(this.comm, 'clientConnect', (message) => {
 | 
			
		||||
      console.log(`App clientConnect event: resetClientId ${message.resetClientId} version ${message.serverVersion}`);
 | 
			
		||||
      this._settings(message.settings);
 | 
			
		||||
      this.login.updateProfileFromServer(message.profile);
 | 
			
		||||
      if (message.serverVersion === 'dead' || (this._serverVersion && this._serverVersion !== message.serverVersion)) {
 | 
			
		||||
        console.log("Upgrading...");
 | 
			
		||||
        // Server has upgraded.  Upgrade client.  TODO: be gentle and polite.
 | 
			
		||||
@ -143,12 +130,6 @@ export class App extends DisposableWithEvents {
 | 
			
		||||
      this.topAppModel.notifier.setConnectState(isConnected);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.listenTo(this.comm, 'profileFetch', (message) => {
 | 
			
		||||
      this.login.updateProfileFromServer(message.data);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.listenTo(this.comm, 'clientLogout', () => this.login.onLogout());
 | 
			
		||||
 | 
			
		||||
    this.listenTo(this.comm, 'docShutdown', () => {
 | 
			
		||||
      console.log("Received docShutdown");
 | 
			
		||||
      // Reload on next tick, to let other objects process 'docShutdown' before they get disposed.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user