mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Cleanup removing some old unused files, fixing logo.css, and removing #grist-app.
Summary: - Move logo.css to core, since it's not included otherwise - Remove unused old DocList and ViewLinker files. - Remove #grist-app div that was only serving to supply a background Test Plan: No changes of behavior, existing tests should pass. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2634
This commit is contained in:
parent
bd6a54e901
commit
d2ad5edc46
@ -13,15 +13,12 @@
|
|||||||
--color-link-bright: orange;
|
--color-link-bright: orange;
|
||||||
|
|
||||||
--color-start-page-bg: #f0f0f0;
|
--color-start-page-bg: #f0f0f0;
|
||||||
--color-doclist-bg: #fcfcfc;
|
|
||||||
|
|
||||||
--color-navbar-bg: var(--color-logo-bg);
|
--color-navbar-bg: var(--color-logo-bg);
|
||||||
--color-navbar-btn-bg: #fefefe;
|
--color-navbar-btn-bg: #fefefe;
|
||||||
--color-navbar-btn-bg-hover: #f6f6f6;
|
--color-navbar-btn-bg-hover: #f6f6f6;
|
||||||
--color-navbar-btn-disabled: #ccc;
|
--color-navbar-btn-disabled: #ccc;
|
||||||
|
|
||||||
--color-header-doclist: #6d6dde;
|
|
||||||
|
|
||||||
--color-tab-bar-bg: #d6d6d6;
|
--color-tab-bar-bg: #d6d6d6;
|
||||||
|
|
||||||
--color-border-light: #ddd;
|
--color-border-light: #ddd;
|
||||||
@ -35,8 +32,6 @@
|
|||||||
--color-btn-accept: #3eda2c;
|
--color-btn-accept: #3eda2c;
|
||||||
|
|
||||||
--layout-top-spacer: 20px;
|
--layout-top-spacer: 20px;
|
||||||
--layout-doclist-header-height: 42px;
|
|
||||||
--layout-doclist-height: calc(100vh - 2*var(--layout-top-spacer));
|
|
||||||
|
|
||||||
--color-list-row-hover: #f0f0f0;
|
--color-list-row-hover: #f0f0f0;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* global $, window, document */
|
/* global $, window */
|
||||||
|
|
||||||
const {App} = require('./ui/App');
|
const {App} = require('./ui/App');
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ const ko = require('knockout');
|
|||||||
setupKoDisposal(ko);
|
setupKoDisposal(ko);
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
window.gristApp = App.create(null, document.getElementById('grist-app'));
|
window.gristApp = App.create(null);
|
||||||
// Set from the login tests to stub and un-stub functions during execution.
|
// Set from the login tests to stub and un-stub functions during execution.
|
||||||
window.loginTestSandbox = null;
|
window.loginTestSandbox = null;
|
||||||
|
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
#grist-app {
|
|
||||||
background: url('img/gplaypattern.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.start-doclist {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
top: var(--layout-top-spacer);
|
|
||||||
height: calc(100% - 2*var(--layout-top-spacer));
|
|
||||||
|
|
||||||
width: 90vw;
|
|
||||||
min-width: 600px;
|
|
||||||
max-width: 900px;
|
|
||||||
|
|
||||||
background-color: var(--color-doclist-bg);
|
|
||||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.5);
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
background-color: var(--color-logo-bg);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions-logo {
|
|
||||||
height: var(--layout-doclist-header-height);
|
|
||||||
padding: .5rem;
|
|
||||||
|
|
||||||
background: url('img/logo-grist-transp.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-actions {
|
|
||||||
margin: 1rem auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-doclist {
|
|
||||||
height: var(--layout-doclist-header-height);
|
|
||||||
min-height: var(--layout-doclist-header-height);
|
|
||||||
|
|
||||||
background: linear-gradient(to bottom, #f9f9f9, #f0f0f0);
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-link {
|
|
||||||
margin: .5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-header-doclist);
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-section {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-sidepane-wrapper {
|
|
||||||
width: 180px;
|
|
||||||
min-width: 180px;
|
|
||||||
|
|
||||||
border-right: 1px solid var(--color-border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-sidepane {
|
|
||||||
margin: .5rem auto;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-sidepane.b-actions {
|
|
||||||
margin: .5rem 2rem;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-doclist {
|
|
||||||
height: var(--layout-doclist-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-body-wrapper {
|
|
||||||
margin-top: 2px;
|
|
||||||
height: calc(100% - 2px - var(--layout-doclist-header-height));
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane {
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px 1px 1px 4px;
|
|
||||||
|
|
||||||
font-weight: 400;
|
|
||||||
color: #000;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 18px;
|
|
||||||
}
|
|
||||||
.g-btn.mod-doclist__sidepane:hover {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane.fit-text {
|
|
||||||
font-size: 1.0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane.el-login {
|
|
||||||
border-color: var(--color-btn-login);
|
|
||||||
background-color: var(--color-btn-login-background);
|
|
||||||
}
|
|
||||||
.g-btn.mod-doclist__sidepane.el-login:hover {
|
|
||||||
background-color: var(--color-btn-login);
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane.el-createdoc {
|
|
||||||
border-color: var(--color-btn-createdoc);
|
|
||||||
}
|
|
||||||
.g-btn.mod-doclist__sidepane.el-createdoc:hover {
|
|
||||||
background-color: var(--color-btn-createdoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane.el-editteam {
|
|
||||||
margin-top: 1rem;
|
|
||||||
border-color: #7e25ff;
|
|
||||||
}
|
|
||||||
.g-btn.mod-doclist__sidepane.el-editteam:hover {
|
|
||||||
background-color: #7e25ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__sidepane.el-uploaddoc {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
border-color: var(--color-btn-uploaddoc);
|
|
||||||
}
|
|
||||||
.g-btn.mod-doclist__sidepane.el-uploaddoc:hover {
|
|
||||||
background-color: var(--color-btn-uploaddoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn.mod-doclist__invite {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-invite {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.doclist-invite-bottom-row {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1.0rem;
|
|
||||||
color: var(--color-hint-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-doclist__sidepane.el-divider {
|
|
||||||
border-bottom: 1px solid var(--color-border-light);
|
|
||||||
height: 1px;
|
|
||||||
width: 90%;
|
|
||||||
margin: 15px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-uploaddoc__bar {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 0%;
|
|
||||||
left: 0px;
|
|
||||||
top: 0px;
|
|
||||||
padding-top: 1px;
|
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
background-color: var(--color-btn-uploaddoc);
|
|
||||||
|
|
||||||
font-size: 2rem;
|
|
||||||
|
|
||||||
transition: width .2s ease-out, opacity .2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-btn {
|
|
||||||
margin: 0;
|
|
||||||
padding: .5rem .8rem;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g-list {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-logo-img {
|
|
||||||
width: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-header {
|
|
||||||
top: 0;
|
|
||||||
height: var(--layout-doclist-header-height);
|
|
||||||
min-height: var(--layout-doclist-header-height);
|
|
||||||
|
|
||||||
padding: .2rem 1rem;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
background: linear-gradient(to bottom, #f0f0f0, #e6e6e6);
|
|
||||||
box-shadow: 0 2px 2px -1px rgba(0,0,0,0.5);
|
|
||||||
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-row {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--color-border-light);
|
|
||||||
|
|
||||||
transition: background-color .2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-row:hover {
|
|
||||||
background-color: var(--color-list-row-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-sample {
|
|
||||||
color: var(--color-header-doclist);
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-column {
|
|
||||||
min-width: 120px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-column.doclist-column__name {
|
|
||||||
min-width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-column.doclist-column__download, .doclist-column.doclist-column__delete {
|
|
||||||
min-width: 15px;
|
|
||||||
color: #777;
|
|
||||||
font-size: 1.0rem;
|
|
||||||
text-align: right;
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-column.mod-header {
|
|
||||||
padding-left: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-column__size {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doclist-download_link {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glyphicon.mod-doclist-column {
|
|
||||||
padding-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.uploadAlert {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gupload {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gupload_item {
|
|
||||||
position: relative;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gupload_bar {
|
|
||||||
position: absolute;
|
|
||||||
z-index: -1;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
background-color: lightgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gupload_failed {
|
|
||||||
background-color: #FFA0A0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gupload_info {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
@ -1,331 +0,0 @@
|
|||||||
/* global window, $ */
|
|
||||||
|
|
||||||
const bluebird = require('bluebird');
|
|
||||||
const _ = require('underscore');
|
|
||||||
const ko = require('knockout');
|
|
||||||
const BackboneEvents = require('backbone').Events;
|
|
||||||
|
|
||||||
const gutil = require('app/common/gutil');
|
|
||||||
const version = require('app/common/version');
|
|
||||||
|
|
||||||
const dispose = require('../lib/dispose');
|
|
||||||
const dom = require('../lib/dom');
|
|
||||||
const kd = require('../lib/koDom');
|
|
||||||
const koSession = require('../lib/koSession');
|
|
||||||
const {IMPORTABLE_EXTENSIONS, selectFiles} = require('../lib/uploads');
|
|
||||||
|
|
||||||
const commands = require('./commands');
|
|
||||||
const {urlState} = require('app/client/models/gristUrlState');
|
|
||||||
const {showConfirmDialog} = require('./Confirm');
|
|
||||||
|
|
||||||
const sortByFuncs = {
|
|
||||||
'name': (a, b) => a.localeCompare(b),
|
|
||||||
'mtime': (a, b) => gutil.nativeCompare(a, b),
|
|
||||||
'size': (a, b) => gutil.nativeCompare(a, b)
|
|
||||||
};
|
|
||||||
|
|
||||||
function DocList(app) {
|
|
||||||
this.app = app;
|
|
||||||
this.docListModel = app.docListModel;
|
|
||||||
this.docs = app.docListModel.docs;
|
|
||||||
this._docInvites = app.docListModel.docInvites;
|
|
||||||
|
|
||||||
// Default sort is by modified time in descending order
|
|
||||||
// TODO: This would be better yet as a user preference, since this still resets to default for
|
|
||||||
// each browser window.
|
|
||||||
this.sortBy = koSession.sessionValue('docListSortBy', 'mtime');
|
|
||||||
this.sortAsc = koSession.sessionValue('docListSortDir', -1); // 1 for asc, -1 for desc
|
|
||||||
|
|
||||||
let compareFunc = this.autoDispose(ko.computed(() => {
|
|
||||||
let attrib = this.sortBy();
|
|
||||||
let asc = this.sortAsc();
|
|
||||||
let compareFunc = sortByFuncs[attrib];
|
|
||||||
return (a, b) => gutil.nativeCompare(a.tag, b.tag) || compareFunc(a[attrib], b[attrib]) * asc;
|
|
||||||
}));
|
|
||||||
this.sortedDocs = this.autoDispose(ko.computed(() =>
|
|
||||||
this.docs.all().sort(compareFunc())).extend({rateLimit: 0}));
|
|
||||||
|
|
||||||
this.docListModel.refreshDocList();
|
|
||||||
|
|
||||||
this.autoDispose(this.app.login.isLoggedIn.subscribe(loggedIn => {
|
|
||||||
// Refresh the doc list to show/hide docs and invites belonging to the logged in user.
|
|
||||||
this.docListModel.refreshDocList();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
dispose.makeDisposable(DocList);
|
|
||||||
_.extend(DocList.prototype, BackboneEvents);
|
|
||||||
|
|
||||||
// TODO: Factor out common code from DocList and NavBar (e.g. status indicator)
|
|
||||||
DocList.prototype.buildDom = function() {
|
|
||||||
let currentStatus = ko.observable('OK');
|
|
||||||
|
|
||||||
this.listenTo(this.app.comm, 'connectionStatus', (message, status) => {
|
|
||||||
console.warn('connectionStatus', message, status);
|
|
||||||
currentStatus(status);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.autoDispose(
|
|
||||||
dom('div.start-doclist.flexhbox',
|
|
||||||
dom('section.doclist-section.section-sidepane-wrapper.flexvbox',
|
|
||||||
parts.sidepane.header(() => this.docListModel.refreshDocList()),
|
|
||||||
parts.sidepane.connection(currentStatus),
|
|
||||||
dom('div.section-sidepane.b-actions.flexnone.flexvbox',
|
|
||||||
kd.maybe(() => this.app.features().signin, () =>
|
|
||||||
dom('div',
|
|
||||||
parts.sidepane.loginButton(this.app.login),
|
|
||||||
dom('div.mod-doclist__sidepane.el-divider')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
parts.sidepane.createDocButton(this.createNewDoc.bind(this)),
|
|
||||||
window.electronOpenDialog ? parts.sidepane.openDocButton() : null,
|
|
||||||
parts.sidepane.uploadDocButton(this.uploadNewDoc.bind(this))
|
|
||||||
),
|
|
||||||
dom('div.flexitem'),
|
|
||||||
parts.sidepane.footer(commands.allCommands.help.run)),
|
|
||||||
dom('section.doclist-section.section-doclist-wrapper.flexitem', this._buildDoclistBody())));
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._buildDoclistBody = function() {
|
|
||||||
return dom('div.section-doclist',
|
|
||||||
dom('div.doclist-header.flexhbox',
|
|
||||||
dom('div.doclist-column.doclist-column__name.mod-header.flexitem', 'Name',
|
|
||||||
parts.doclist.ascDescArrow(this.sortBy, this.sortAsc, 'name'),
|
|
||||||
dom.on('click', () => updateSort(this.sortBy, this.sortAsc, 'name'))),
|
|
||||||
dom('div.doclist-column.doclist-column__size', 'Size',
|
|
||||||
parts.doclist.ascDescArrow(this.sortBy, this.sortAsc, 'size'),
|
|
||||||
dom.on('click', () => updateSort(this.sortBy, this.sortAsc, 'size'))),
|
|
||||||
dom('div.doclist-column.doclist-column__modified', 'Modified',
|
|
||||||
parts.doclist.ascDescArrow(this.sortBy, this.sortAsc, 'mtime'),
|
|
||||||
dom.on('click', () => updateSort(this.sortBy, this.sortAsc, 'mtime'))),
|
|
||||||
dom('div.doclist-column.doclist-column__delete')
|
|
||||||
),
|
|
||||||
dom('div.doclist-body-wrapper',
|
|
||||||
dom('div.doclist-body',
|
|
||||||
kd.scope(this.sortedDocs, docs =>
|
|
||||||
docs.map(docObj => parts.doclist.listDoc(docObj, this._onClickItem.bind(this),
|
|
||||||
this._confirmRemoveDoc.bind(this)))),
|
|
||||||
kd.foreach(this._docInvites, inviteObj =>
|
|
||||||
parts.doclist.listInvite(inviteObj, this._downloadSharedDoc.bind(this),
|
|
||||||
this._confirmDeclineInvite.bind(this))))));
|
|
||||||
};
|
|
||||||
|
|
||||||
const parts = {
|
|
||||||
|
|
||||||
sidepane: {
|
|
||||||
|
|
||||||
connection: (statusObs) =>
|
|
||||||
dom('div.section-sidepane.b-status.flexhbox',
|
|
||||||
dom('div.gnotifier', kd.cssClass(() => 'g-status-' + statusObs())),
|
|
||||||
dom('span', 'Connection: '),
|
|
||||||
kd.text(statusObs)),
|
|
||||||
|
|
||||||
header: (headerCommand) =>
|
|
||||||
dom('div.header-actions',
|
|
||||||
dom('div.header-actions-logo', // Logo set in css
|
|
||||||
{title: `Ver ${version.version} (${version.gitcommit})`},
|
|
||||||
dom.on('click', headerCommand))),
|
|
||||||
|
|
||||||
footer: (helpCommand) =>
|
|
||||||
dom('div.footer-actions.flexvbox',
|
|
||||||
dom('a.doclist-link', { href: 'help', target: '_blank' }, 'Help'),
|
|
||||||
dom('a.doclist-link', { href: 'help/contact-us', target: '_blank'}, 'Contact'),
|
|
||||||
dom('a.doclist-link', { href: 'https://www.getgrist.com', target: '_blank'}, 'About')),
|
|
||||||
|
|
||||||
loginButton: (login) =>
|
|
||||||
dom('div.g-btn.mod-doclist__sidepane.el-login',
|
|
||||||
dom.testId('DocList_login'),
|
|
||||||
kd.toggleClass('fit-text', () => login.isLoggedIn()),
|
|
||||||
kd.text(login.buttonText),
|
|
||||||
dom.on('click', e => { login.onClick(e); })),
|
|
||||||
|
|
||||||
createDocButton: createNewDoc =>
|
|
||||||
dom('div.g-btn.mod-doclist__sidepane.el-createdoc',
|
|
||||||
dom.testId('DocList_createDoc'),
|
|
||||||
'Create document',
|
|
||||||
dom.on('click', createNewDoc)),
|
|
||||||
|
|
||||||
uploadDocButton: uploadNewDoc => {
|
|
||||||
let uploadStatusIcons = {
|
|
||||||
'uploading': 'glyphicon-upload',
|
|
||||||
'success': 'glyphicon-ok-sign',
|
|
||||||
'error': 'glyphicon-exclamation-sign'
|
|
||||||
};
|
|
||||||
let uploadStatus = ko.observable(null);
|
|
||||||
|
|
||||||
let uploadBar;
|
|
||||||
let uploadProgress = percentage => uploadBar.style.width = percentage + '%';
|
|
||||||
|
|
||||||
return dom('div.g-btn.mod-doclist__sidepane.el-uploaddoc',
|
|
||||||
dom.testId('DocList_uploadBtn'),
|
|
||||||
uploadBar = dom('div.el-uploaddoc__bar',
|
|
||||||
kd.show(uploadStatus),
|
|
||||||
dom('span.glyphicon.glyphicon-upload',
|
|
||||||
kd.cssClass(() => uploadStatusIcons[uploadStatus()])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
dom('span',
|
|
||||||
kd.style('visibility', () => uploadStatus() === null ? 'visible' : 'hidden'),
|
|
||||||
'Import document'
|
|
||||||
),
|
|
||||||
dom.on('click', () => uploadNewDoc(uploadStatus, uploadProgress))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
openDocButton: () => dom('div.g-btn.mod-doclist__sidepane.el-uploaddoc',
|
|
||||||
dom('span', 'Open document'),
|
|
||||||
dom.on('click', window.electronOpenDialog)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
doclist: {
|
|
||||||
|
|
||||||
ascDescArrow: (sortBy, sortAsc, attrib) =>
|
|
||||||
dom('span', kd.text(() => sortBy() === attrib ? (sortAsc() > 0 ? '▲' : '▼') : '')),
|
|
||||||
|
|
||||||
listDoc: (docObj, onClick, onDelete) => {
|
|
||||||
let mtime = new Date(docObj.mtime);
|
|
||||||
return dom('div.doclist-row.flexhbox',
|
|
||||||
kd.toggleClass('doclist-sample', docObj.tag === 'sample'),
|
|
||||||
dom('div.doclist-column.doclist-column__name.flexitem',
|
|
||||||
dom('span.glyphicon.glyphicon-file.mod-doclist-column'),
|
|
||||||
dom('a', {
|
|
||||||
href: urlState().makeUrl({doc: docObj.name})
|
|
||||||
},
|
|
||||||
kd.toggleClass('doclist-sample', docObj.tag === 'sample'),
|
|
||||||
docObj.tag ? `[${gutil.capitalize(docObj.tag)}] ` : null,
|
|
||||||
docObj.name,
|
|
||||||
dom.on('click', (ev) => onClick(ev, docObj))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
dom('div.doclist-column.doclist-column__size', gutil.byteString(docObj.size)),
|
|
||||||
dom('div.doclist-column.doclist-column__modified',
|
|
||||||
mtime.toLocaleDateString() + ' ' + mtime.toLocaleTimeString()),
|
|
||||||
// Show a downlink link for each file in the hosted version, but not in the electron version.
|
|
||||||
window.isRunningUnderElectron ? null : dom('div.doclist-column.doclist-column__download.glyphicon.glyphicon-download-alt',
|
|
||||||
dom('a.doclist-download_link', {
|
|
||||||
href: 'download?' + $.param({doc: docObj.name}),
|
|
||||||
download: `${docObj.name}.grist`
|
|
||||||
})
|
|
||||||
),
|
|
||||||
dom('div.doclist-column.doclist-column__delete.glyphicon.glyphicon-trash',
|
|
||||||
dom.on('click', () => onDelete(docObj.name))));
|
|
||||||
},
|
|
||||||
|
|
||||||
listInvite: (docObj, onDownload, onDecline) => {
|
|
||||||
const name = docObj.senderName;
|
|
||||||
const email = docObj.senderEmail;
|
|
||||||
return dom('div.doclist-row.doclist-invite.flexhbox',
|
|
||||||
dom('div.flexhbox.doclist-invite-top-row',
|
|
||||||
dom('div.doclist-column.doclist-column__name.flexitem',
|
|
||||||
dom('span.glyphicon.glyphicon-download-alt.mod-doclist-column'),
|
|
||||||
dom.on('click', () => onDownload(docObj)),
|
|
||||||
'[Invite] ',
|
|
||||||
docObj.name
|
|
||||||
),
|
|
||||||
dom('div.doclist-column.doclist-column__size'),
|
|
||||||
dom('div.doclist-column.doclist-column__modified'),
|
|
||||||
dom('div.doclist-column.doclist-column__delete.glyphicon.glyphicon-remove',
|
|
||||||
dom.on('click', () => onDecline(docObj)))
|
|
||||||
),
|
|
||||||
dom('div.flexhbox.doclist-invite-bottom-row', 'Sent by: ' + (name ? `${name} (${email})` : email))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._downloadSharedDoc = function(docObj) {
|
|
||||||
return this.app.comm.downloadSharedDoc(docObj.docId, docObj.name)
|
|
||||||
.then(() => this.docListModel.refreshDocList());
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._confirmDeclineInvite = function(docObj) {
|
|
||||||
showConfirmDialog(`Ignore invite to ${docObj.name}?`, 'Ignore', () => this._ignoreInvite(docObj));
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._confirmRemoveDoc = function(docName) {
|
|
||||||
if (window.isRunningUnderElectron) {
|
|
||||||
showConfirmDialog(`Move ${docName} to trash?`, 'Move to trash', () => this.app.comm.deleteDoc(docName, false));
|
|
||||||
} else {
|
|
||||||
showConfirmDialog(`Delete ${docName} permanently?`, 'Delete', () => this.app.comm.deleteDoc(docName, true));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._ignoreInvite = function(docObj) {
|
|
||||||
return this.app.comm.ignoreLocalInvite(docObj.docId)
|
|
||||||
.then(() => this.docListModel.refreshDocList());
|
|
||||||
};
|
|
||||||
|
|
||||||
// commands useable while title is active
|
|
||||||
DocList.fileEditorCommands = {
|
|
||||||
accept: function() { this.finishEditingFileName(true); },
|
|
||||||
cancel: function() { this.finishEditingFileName(false); },
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype.createNewDoc = function(source, event) {
|
|
||||||
return this.app.comm.createNewDoc()
|
|
||||||
.then(docName => urlState().pushUrl({doc: docName}, {avoidReload: true}))
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype._onClickItem = function(ev, docObj) {
|
|
||||||
ev.preventDefault();
|
|
||||||
if (docObj.tag === 'sample') {
|
|
||||||
// Note that we don't expect this to fail, so an error will show up in the NotifyBox.
|
|
||||||
this.app.comm.importSampleDoc(docObj.name)
|
|
||||||
.then(docName => urlState().pushUrl({doc: docName}, {avoidReload: true}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// To avoid a page reload, we handle the change of url. Previously we used fragments,
|
|
||||||
// so the browser knew there was no page change going on. Now, we are using real urls
|
|
||||||
// and we have to hold the browser's hand.
|
|
||||||
urlState().pushUrl({doc: docObj.name}, {avoidReload: true});
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
DocList.prototype.uploadNewDoc = function(uploadStatus, uploadProgress) {
|
|
||||||
function progress(percent) {
|
|
||||||
// We use one progress bar to combine the time to upload and the time to import in 40%-60%
|
|
||||||
// split. TODO The second part needs to be done; currently it stops at 40%, but at least the
|
|
||||||
// user can tell that there is still something to wait for.
|
|
||||||
const p = percent * 0.4;
|
|
||||||
uploadProgress(p);
|
|
||||||
if (p > 0) { uploadStatus('uploading'); }
|
|
||||||
}
|
|
||||||
return bluebird.try(() => {
|
|
||||||
return selectFiles({multiple: true, extensions: IMPORTABLE_EXTENSIONS},
|
|
||||||
progress);
|
|
||||||
})
|
|
||||||
.then(uploadResult => {
|
|
||||||
// Put together a text summary of what got uploaded, currently only for debugging.
|
|
||||||
const summaryList = uploadResult.files.map(f => `${f.origName} (${f.size})`);
|
|
||||||
console.log('Upload summary:', summaryList.join(", "));
|
|
||||||
// TODO: This step should include its own progress callback, and the progress indicator should
|
|
||||||
// combine the two steps, e.g. in 40%-60% split.
|
|
||||||
return this.app.comm.importDoc(uploadResult.uploadId);
|
|
||||||
})
|
|
||||||
.then(docName => {
|
|
||||||
uploadStatus('success');
|
|
||||||
return bluebird.delay(500) // Let the user see the OK icon briefly.
|
|
||||||
.then(() => urlState().pushUrl({doc: docName}, {avoidReload: true}));
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("Upload failed: %s", err);
|
|
||||||
uploadStatus('error');
|
|
||||||
return bluebird.delay(2000); // Let the user see the error icon.
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
uploadStatus(null);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateSort(sortObs, sortAsc, sortValue) {
|
|
||||||
let currSort = sortObs();
|
|
||||||
if (currSort === sortValue) {
|
|
||||||
sortAsc(-1 * sortAsc());
|
|
||||||
} else {
|
|
||||||
sortObs(sortValue);
|
|
||||||
sortAsc(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DocList;
|
|
@ -1,334 +0,0 @@
|
|||||||
var _ = require('underscore');
|
|
||||||
var ko = require('knockout');
|
|
||||||
var gutil = require('app/common/gutil');
|
|
||||||
var dispose = require('../lib/dispose');
|
|
||||||
var dom = require('../lib/dom');
|
|
||||||
var kd = require('../lib/koDom');
|
|
||||||
var commands = require('./commands');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
||||||
*/
|
|
||||||
var G = require('../lib/browserGlobals').get('window', 'document', '$');
|
|
||||||
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
function ViewLinkerNode(section, column, tableId, primaryTableId) {
|
|
||||||
this.section = section;
|
|
||||||
this.sectionRef = section ? section.getRowId() : 0;
|
|
||||||
this.col = column;
|
|
||||||
this.colRef = column ? column.getRowId() : 0;
|
|
||||||
this.tableId = tableId;
|
|
||||||
this.primaryTableId = primaryTableId;
|
|
||||||
this.nodeId = this.sectionRef + ':' + this.colRef; // Uniquely identifies the node.
|
|
||||||
this.linkIconDom = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewLinkerNode.prototype.isLinked = function() {
|
|
||||||
return this.section && this.section.activeLinkSrcSectionRef() &&
|
|
||||||
this.section.activeLinkTargetColRef() === this.colRef;
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinkerNode.prototype.isValidLinkTo = function(targetNode) {
|
|
||||||
let tablesMatch = (this.tableId === targetNode.tableId ||
|
|
||||||
(this.primaryTableId === targetNode.primaryTableId && !this.col && !targetNode.col));
|
|
||||||
|
|
||||||
// There are table-to-table links (cursor sync), table-to-column (filter), column-to-table
|
|
||||||
// (cursor), and column-to-column (filter).
|
|
||||||
|
|
||||||
// The table must match.
|
|
||||||
if (!tablesMatch) { return false; }
|
|
||||||
|
|
||||||
// The target must not be already linked.
|
|
||||||
if (targetNode.section && targetNode.section.activeLinkSrcSectionRef()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.section) {
|
|
||||||
// The link must not create a cycle.
|
|
||||||
for (let sec = this.section; sec.getRowId(); sec = sec.linkSrcSection()) {
|
|
||||||
if (targetNode.sectionRef === sec.getRowId()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
ViewLinkerNode.prototype.linkCoord = function() {
|
|
||||||
var rect = this.linkIconDom.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
left: rect.left + rect.width / 2,
|
|
||||||
top: rect.top + rect.height / 2
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewLinker - Builds GUI for linking viewSections in viewModel
|
|
||||||
*/
|
|
||||||
function ViewLinker(viewModel) {
|
|
||||||
this.viewModel = viewModel;
|
|
||||||
this.viewSections = this.viewModel.viewSections().all();
|
|
||||||
|
|
||||||
this.clicked = ko.observable(null);
|
|
||||||
|
|
||||||
// For each section, the pair (section, colRef) represents a linkable node for every reference
|
|
||||||
// column, and for colRef 0 (which corresponds to the special "id" column, or the table itself).
|
|
||||||
this.allNodes = [];
|
|
||||||
for (let section of this.viewSections) {
|
|
||||||
this.allNodes.push(...ViewLinker.createNodes(section, section.table()));
|
|
||||||
}
|
|
||||||
this.nodesBySection = _.groupBy(this.allNodes, 'sectionRef');
|
|
||||||
this.nodesById = _.indexBy(this.allNodes, 'nodeId');
|
|
||||||
|
|
||||||
// A list of coordinate data objects for coordinates in no particular order.
|
|
||||||
this.coordinates = null;
|
|
||||||
|
|
||||||
this.boundMouseMove = this.handleMouseMove.bind(this);
|
|
||||||
this.boundWindowResize = this.handleWindowResize.bind(this);
|
|
||||||
|
|
||||||
this.autoDisposeCallback(function() {
|
|
||||||
G.$(G.window).off('mousemove', this.boundMouseMove);
|
|
||||||
G.$(G.window).off('resize', this.boundWindowResize);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.canvas = this.autoDispose(dom('canvas.linker_canvas'));
|
|
||||||
this.buttons = this.autoDispose(
|
|
||||||
dom('div.linker_save_btns',
|
|
||||||
dom('div.linker_btn', 'Apply',
|
|
||||||
dom.on('click', () => {
|
|
||||||
this.viewModel.isLinking(false);
|
|
||||||
})
|
|
||||||
),
|
|
||||||
dom('div.linker_btn', 'Apply & Save',
|
|
||||||
dom.on('click', () => {
|
|
||||||
commands.allCommands.saveLinks.run();
|
|
||||||
this.viewModel.isLinking(false);
|
|
||||||
})
|
|
||||||
),
|
|
||||||
dom('div.linker_btn', 'Cancel',
|
|
||||||
dom.on('click', () => {
|
|
||||||
commands.allCommands.revertLinks.run();
|
|
||||||
this.viewModel.isLinking(false);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
dom.id('grist-app').appendChild(this.canvas);
|
|
||||||
dom.id('view_content').appendChild(this.buttons);
|
|
||||||
this.ctx = this.canvas.getContext("2d");
|
|
||||||
|
|
||||||
// Must monitor link values and redraw arrows on changes since undo-ing/redo-ing may
|
|
||||||
// affect them.
|
|
||||||
this.autoDispose(ko.computed(() => {
|
|
||||||
for (let section of this.viewSections) {
|
|
||||||
section.linkSrcSectionRef();
|
|
||||||
section.linkSrcColRef();
|
|
||||||
section.linkTargetColRef();
|
|
||||||
}
|
|
||||||
setTimeout(this.resetArrows.bind(this), 0);
|
|
||||||
}));
|
|
||||||
G.$(G.window).on('resize', this.boundWindowResize);
|
|
||||||
|
|
||||||
this.handleWindowResize();
|
|
||||||
}
|
|
||||||
dispose.makeDisposable(ViewLinker);
|
|
||||||
|
|
||||||
ViewLinker.createNodes = function(section, table) {
|
|
||||||
const nodes = [];
|
|
||||||
nodes.push(new ViewLinkerNode(section, null, table.tableId(),
|
|
||||||
table.primaryTableId()));
|
|
||||||
for (let column of table.columns().peek()) {
|
|
||||||
let tableId = gutil.removePrefix(column.type(), "Ref:");
|
|
||||||
if (tableId) {
|
|
||||||
nodes.push(new ViewLinkerNode(section, column, tableId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fills coordinates object with values and re-renders canvas.
|
|
||||||
ViewLinker.prototype.resetArrows = function() {
|
|
||||||
this.resetCoordinates();
|
|
||||||
this.redrawArrows();
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinker.prototype.onClickNode = function(event, node) {
|
|
||||||
if (!this.clicked()) {
|
|
||||||
this.clicked(node);
|
|
||||||
G.$(G.window).on('mousemove', this.boundMouseMove);
|
|
||||||
} else {
|
|
||||||
this.connectLink(node);
|
|
||||||
this.clicked(null);
|
|
||||||
G.$(G.window).off('mousemove', this.boundMouseMove);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinker.prototype.onClickBackground = function(event) {
|
|
||||||
if (this.clicked()) {
|
|
||||||
this.clicked(null);
|
|
||||||
G.$(G.window).off('mousemove', this.boundMouseMove);
|
|
||||||
this.redrawArrows();
|
|
||||||
} else {
|
|
||||||
this.viewModel.isLinking(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinker.prototype.handleMouseMove = function(event) {
|
|
||||||
this.redrawArrows();
|
|
||||||
let coord = this.clicked().linkCoord();
|
|
||||||
drawArrow(this.ctx, coord.left, coord.top, event.clientX, event.clientY);
|
|
||||||
this.ctx.stroke();
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinker.prototype.handleWindowResize = function(event) {
|
|
||||||
// Canvas must be scaled up in a particular way for improved resolution.
|
|
||||||
var w = G.$(G.window);
|
|
||||||
var windowWidth = w.innerWidth();
|
|
||||||
var windowHeight = w.innerHeight();
|
|
||||||
this.ctx.canvas.style.width = windowWidth + 'px';
|
|
||||||
this.ctx.canvas.style.height = windowHeight + 'px';
|
|
||||||
this.ctx.canvas.width = windowWidth * 2;
|
|
||||||
this.ctx.canvas.height = windowHeight * 2;
|
|
||||||
this.ctx.scale(2, 2);
|
|
||||||
setTimeout(this.resetArrows.bind(this), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// To be called by each viewSection when the ViewLinker is active.
|
|
||||||
ViewLinker.prototype.buildLeafDom = function(viewSection) {
|
|
||||||
var sectionRef = viewSection.getRowId();
|
|
||||||
var mainNode = this.nodesById[sectionRef + ':0'];
|
|
||||||
var sectionNodes = this.nodesBySection[sectionRef];
|
|
||||||
var getOtherSections = () => this.clicked() && this.clicked().sectionRef !== sectionRef;
|
|
||||||
|
|
||||||
return dom('div.g_record_layout_linking',
|
|
||||||
dom.on('click', event => { this.onClickBackground(event); }),
|
|
||||||
dom('div.linker_box',
|
|
||||||
dom('div.linker_box_section',
|
|
||||||
dom('div.linker_box_header', 'Scroll',
|
|
||||||
kd.toggleClass('visible', getOtherSections)
|
|
||||||
),
|
|
||||||
this.buildLinkRowDom(viewSection, mainNode)
|
|
||||||
),
|
|
||||||
dom('div.linker_box_columns',
|
|
||||||
dom('div.linker_box_header', 'Filter',
|
|
||||||
kd.toggleClass('visible', () => getOtherSections() && sectionNodes.length > 1)
|
|
||||||
),
|
|
||||||
this.nodesBySection[sectionRef].map(node => {
|
|
||||||
if (!node.col) { return null; }
|
|
||||||
return this.buildLinkRowDom(viewSection, node);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ViewLinker.prototype.buildLinkRowDom = function(viewSection, node) {
|
|
||||||
var sectionRef = viewSection.getRowId();
|
|
||||||
var tableId = viewSection.table().tableTitle();
|
|
||||||
|
|
||||||
var isAvailable = ko.computed(() => (this.clicked() ?
|
|
||||||
this.clicked().isValidLinkTo(node) :
|
|
||||||
// See if there are any nodes this could possibly link to.
|
|
||||||
this.allNodes.some(tgt => node.isValidLinkTo(tgt))
|
|
||||||
));
|
|
||||||
|
|
||||||
return dom('div.section_link.section_link_' + sectionRef,
|
|
||||||
dom.autoDispose(isAvailable),
|
|
||||||
// Prevents closing on link mode when clicking in link boxes.
|
|
||||||
dom.on('click', event => { event.stopPropagation(); }),
|
|
||||||
dom('span.view_link.link_' + sectionRef + (node.col ? '_' + node.col.getRowId() : ''),
|
|
||||||
dom.on('click', event => {
|
|
||||||
if (isAvailable()) { this.onClickNode(event, node); }
|
|
||||||
}),
|
|
||||||
node.linkIconDom =
|
|
||||||
dom('span.glyphicon.glyphicon-link.view_link_icon',
|
|
||||||
// Use 'visibility' rather than 'display', to keep size and position available.
|
|
||||||
kd.style('visibility', () => isAvailable() ? 'visible' : 'hidden'),
|
|
||||||
kd.toggleClass('selected_link', () => (node.isLinked() || this.clicked() === node))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
dom('span.link_text', tableId + (node.col ? '.' + node.col.colId() : ''),
|
|
||||||
kd.toggleClass('available_text', isAvailable),
|
|
||||||
kd.toggleClass('selected_text', () => node.isLinked()),
|
|
||||||
dom('span.glyphicon.glyphicon-remove.remove_link_icon',
|
|
||||||
kd.style('visibility', () =>
|
|
||||||
(node.isLinked() && !this.clicked() ? 'visible' : 'hidden')),
|
|
||||||
dom.on('click', () => this.removeNodeLinks(node))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initializes the coordinates array with linked node coordinates.
|
|
||||||
ViewLinker.prototype.resetCoordinates = function() {
|
|
||||||
this.coordinates = [];
|
|
||||||
for (let vs of this.viewSections) {
|
|
||||||
if (vs.activeLinkSrcSectionRef.peek()) {
|
|
||||||
let sourceNodeId = vs.activeLinkSrcSectionRef.peek() + ':' + vs.activeLinkSrcColRef.peek();
|
|
||||||
let targetNodeId = vs.getRowId() + ':' + vs.activeLinkTargetColRef.peek();
|
|
||||||
this.coordinates.push({
|
|
||||||
'from': this.nodesById[sourceNodeId].linkCoord(),
|
|
||||||
'to': this.nodesById[targetNodeId].linkCoord()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draws all pre-existing arrows on the canvas.
|
|
||||||
ViewLinker.prototype.redrawArrows = function() {
|
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.coordinates.forEach(conn =>
|
|
||||||
drawArrow(this.ctx, conn.from.left, conn.from.top, conn.to.left, conn.to.top));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Saves a new link from the clicked node to 'node' to the local list of links.
|
|
||||||
ViewLinker.prototype.connectLink = function(node) {
|
|
||||||
var sourceNode = this.clicked();
|
|
||||||
var section = node.section;
|
|
||||||
section.activeLinkSrcSectionRef(sourceNode.sectionRef);
|
|
||||||
section.activeLinkSrcColRef(sourceNode.colRef);
|
|
||||||
section.activeLinkTargetColRef(node.colRef);
|
|
||||||
this.resetArrows();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Removes links from the given node.
|
|
||||||
ViewLinker.prototype.removeNodeLinks = function(node) {
|
|
||||||
if (!node.isLinked()) { return; }
|
|
||||||
var section = node.section;
|
|
||||||
section.activeLinkSrcSectionRef(0);
|
|
||||||
section.activeLinkSrcColRef(0);
|
|
||||||
section.activeLinkTargetColRef(0);
|
|
||||||
this.resetArrows();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draws an orange arrow with a black outline in context ctx from (origX, origY) to (x, y).
|
|
||||||
function drawArrow(ctx, origX, origY, x, y){
|
|
||||||
ctx.strokeStyle = 'black';
|
|
||||||
ctx.lineWidth = 4;
|
|
||||||
ctx.lineCap = 'round';
|
|
||||||
_drawBasicArrow(ctx, origX, origY, x, y);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.strokeStyle = '#F5A542';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
_drawBasicArrow(ctx, origX, origY, x, y);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draws a single arrow with no outline.
|
|
||||||
function _drawBasicArrow(ctx, origX, origY, x, y) {
|
|
||||||
var l = 10; // length of arrow head
|
|
||||||
var angle = Math.atan2(y - origY, x - origX);
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
ctx.lineTo(x - l*Math.cos(angle - Math.PI/5), y - l*Math.sin(angle - Math.PI/5));
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
ctx.lineTo(x - l*Math.cos(angle + Math.PI/5), y - l*Math.sin(angle + Math.PI/5));
|
|
||||||
ctx.moveTo(origX, origY);
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ViewLinker;
|
|
21
app/client/declarations.d.ts
vendored
21
app/client/declarations.d.ts
vendored
@ -128,27 +128,6 @@ declare module "app/client/components/ViewConfigTab" {
|
|||||||
export = ViewConfigTab;
|
export = ViewConfigTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "app/client/components/ViewLinker" {
|
|
||||||
import {ViewRec} from "app/client/models/DocModel";
|
|
||||||
|
|
||||||
namespace ViewLinker {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
class ViewLinkerNode {
|
|
||||||
public section: any;
|
|
||||||
public sectionRef: number;
|
|
||||||
public col: any;
|
|
||||||
public colRef: number;
|
|
||||||
public isValidLinkTo(node: ViewLinkerNode): boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewLinker {
|
|
||||||
public static create(viewRec: ViewRec): ViewLinker;
|
|
||||||
public static createNodes(section: any|null, table: any): ViewLinker.ViewLinkerNode[];
|
|
||||||
}
|
|
||||||
export = ViewLinker;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "app/client/components/commands" {
|
declare module "app/client/components/commands" {
|
||||||
export class Command {
|
export class Command {
|
||||||
public name: string;
|
public name: string;
|
||||||
|
69
app/client/logo.css
Normal file
69
app/client/logo.css
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#grist-logo-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--color-logo-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo {
|
||||||
|
background: var(--color-bg);
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain {
|
||||||
|
display: inline-block;
|
||||||
|
width: 42px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
margin: 1px;
|
||||||
|
|
||||||
|
border-radius: 22px 0 18px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-flip {
|
||||||
|
border-radius: 0 22px 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-empty {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-col {
|
||||||
|
background: var(--color-logo-col);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-row {
|
||||||
|
background: var(--color-logo-row);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-cell {
|
||||||
|
background: var(--color-logo-cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain {
|
||||||
|
animation: spin-grain 3.2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grist-logo-grain.grain-2 { animation-delay: .4s; }
|
||||||
|
.grist-logo-grain.grain-3 { animation-delay: .8s; }
|
||||||
|
.grist-logo-grain.grain-4 { animation-delay: 1.2s; }
|
||||||
|
.grist-logo-grain.grain-5 { animation-delay: 0s; }
|
||||||
|
.grist-logo-grain.grain-6 { animation-delay: .2s; }
|
||||||
|
.grist-logo-grain.grain-7 { animation-delay: .6s; }
|
||||||
|
.grist-logo-grain.grain-8 { animation-delay: 1.0s; }
|
||||||
|
.grist-logo-grain.grain-9 { animation-delay: 1.4s; }
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes spin-grain {
|
||||||
|
0% {
|
||||||
|
transform: rotateY(0deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotateY(0deg);
|
||||||
|
}
|
||||||
|
}
|
@ -9,16 +9,7 @@ body {
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
background: url('img/gplaypattern.png');
|
||||||
|
|
||||||
#grist-app {
|
|
||||||
height: 100%;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: flex;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
-webkit-flex-wrap: nowrap;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-help {
|
.g-help {
|
||||||
|
@ -50,7 +50,7 @@ export class App extends DisposableWithEvents {
|
|||||||
// we can choose to refresh the client also.
|
// we can choose to refresh the client also.
|
||||||
private _serverVersion: string|null = null;
|
private _serverVersion: string|null = null;
|
||||||
|
|
||||||
constructor(private _appDiv: HTMLElement) {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
commands.init(); // Initialize the 'commands' module using the default command list.
|
commands.init(); // Initialize the 'commands' module using the default command list.
|
||||||
@ -80,7 +80,7 @@ export class App extends DisposableWithEvents {
|
|||||||
G.document.querySelector('#grist-logo-wrapper').remove();
|
G.document.querySelector('#grist-logo-wrapper').remove();
|
||||||
|
|
||||||
// Help pop-up pane
|
// Help pop-up pane
|
||||||
const helpDiv = this._appDiv.appendChild(
|
const helpDiv = document.body.appendChild(
|
||||||
dom('div.g-help',
|
dom('div.g-help',
|
||||||
dom.show(isHelpPaneVisible),
|
dom.show(isHelpPaneVisible),
|
||||||
dom('table.g-help-table',
|
dom('table.g-help-table',
|
||||||
|
@ -38,8 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="grist-app"></div>
|
|
||||||
|
|
||||||
<div id="browser-check-problem" style="display: none;">
|
<div id="browser-check-problem" style="display: none;">
|
||||||
<div class="browser-check-wrapper">
|
<div class="browser-check-wrapper">
|
||||||
<div class="browser-check-message">
|
<div class="browser-check-message">
|
||||||
|
Loading…
Reference in New Issue
Block a user