mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Adding import from google drive to the home screen
Summary: Importing from google drive from home screen (also for anonymous users) Test Plan: Browser tests Reviewers: dsagal, paulfitz Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2943
This commit is contained in:
@@ -63,7 +63,7 @@ export class App extends DisposableWithEvents {
|
||||
this.autoDispose(Clipboard.create(this));
|
||||
} else {
|
||||
// On mobile, we do not want to keep focus on a special textarea (which would cause unwanted
|
||||
// scrolling and showing of mobile keyboard). But we still rely on 'cliboard_focus' and
|
||||
// scrolling and showing of mobile keyboard). But we still rely on 'clipboard_focus' and
|
||||
// 'clipboard_blur' events to know when the "app" has a focus (rather than a particular
|
||||
// input), by making document.body focusable and using a FocusLayer with it as the default.
|
||||
document.body.setAttribute('tabindex', '-1');
|
||||
@@ -172,7 +172,7 @@ export class App extends DisposableWithEvents {
|
||||
this.autoDispose(createAppUI(this.topAppModel, this));
|
||||
}
|
||||
|
||||
// We want to test erors from Selenium, but errors we can trigger using driver.executeScript()
|
||||
// We want to test errors from Selenium, but errors we can trigger using driver.executeScript()
|
||||
// will be impossible for the application to report properly (they seem to be considered not of
|
||||
// "same-origin"). So this silly callback is for tests to generate a fake error.
|
||||
public testTriggerError(msg: string) { throw new Error(msg); }
|
||||
@@ -184,7 +184,7 @@ export class App extends DisposableWithEvents {
|
||||
|
||||
// When called as a dom method, adds the "newui" class when ?newui=1 is set. For example
|
||||
// dom('div.some-old-class', this.app.addNewUIClass(), ...)
|
||||
// Then you may overridde newui styles in CSS by using selectors like:
|
||||
// Then you may override newui styles in CSS by using selectors like:
|
||||
// .some-old-class.newui { ... }
|
||||
public addNewUIClass(): DomElementMethod {
|
||||
return (elem) => { if (this.useNewUI) { elem.classList.add('newui'); } };
|
||||
@@ -206,28 +206,6 @@ export class App extends DisposableWithEvents {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UntrustedContentOrigin use settings. Throws if not defined. The configured
|
||||
* UntrustedContentOrign should not include the port, it is defined at runtime.
|
||||
*/
|
||||
public getUntrustedContentOrigin() {
|
||||
|
||||
if (G.window.isRunningUnderElectron) {
|
||||
// when loaded within webviews it is safe to serve plugin's content from the same domain
|
||||
return "";
|
||||
}
|
||||
|
||||
const origin = G.window.gristConfig.pluginUrl;
|
||||
if (!origin) {
|
||||
throw new Error("Missing untrustedContentOrigin configuration");
|
||||
}
|
||||
if (origin.match(/:[0-9]+$/)) {
|
||||
// Port number already specified, no need to add.
|
||||
return origin;
|
||||
}
|
||||
return origin + ":" + G.window.location.port;
|
||||
}
|
||||
|
||||
// Get the user profile for testing purposes
|
||||
public async testGetProfile(): Promise<any> {
|
||||
const resp = await fetchFromHome('/api/profile/user', {credentials: 'include'});
|
||||
|
||||
@@ -66,7 +66,7 @@ function createMainPage(appModel: AppModel, appObj: App) {
|
||||
}
|
||||
return dom.domComputed(appModel.pageType, (pageType) => {
|
||||
if (pageType === 'home') {
|
||||
return dom.create(pagePanelsHome, appModel);
|
||||
return dom.create(pagePanelsHome, appModel, appObj);
|
||||
} else if (pageType === 'billing') {
|
||||
return domAsync(loadBillingPage().then(bp => dom.create(bp.BillingPage, appModel)));
|
||||
} else if (pageType === 'welcome') {
|
||||
@@ -77,8 +77,8 @@ function createMainPage(appModel: AppModel, appObj: App) {
|
||||
});
|
||||
}
|
||||
|
||||
function pagePanelsHome(owner: IDisposableOwner, appModel: AppModel) {
|
||||
const pageModel = HomeModelImpl.create(owner, appModel);
|
||||
function pagePanelsHome(owner: IDisposableOwner, appModel: AppModel, app: App) {
|
||||
const pageModel = HomeModelImpl.create(owner, appModel, app.clientScope);
|
||||
const leftPanelOpen = Observable.create(owner, true);
|
||||
|
||||
// Set document title to strings like "Home - Grist" or "Org Name - Grist".
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {PluginScreen} from 'app/client/components/PluginScreen';
|
||||
import {guessTimezone} from 'app/client/lib/guessTimezone';
|
||||
import {ImportSourceElement} from 'app/client/lib/ImportSourceElement';
|
||||
import {IMPORTABLE_EXTENSIONS, uploadFiles} from 'app/client/lib/uploads';
|
||||
import {AppModel, reportError} from 'app/client/models/AppModel';
|
||||
import {IProgress} from 'app/client/models/NotifyModel';
|
||||
@@ -22,6 +24,14 @@ export async function docImport(app: AppModel, workspaceId: number|"unsaved"): P
|
||||
|
||||
if (!files.length) { return null; }
|
||||
|
||||
return await fileImport(files, app, workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a document from a file and returns its docId.
|
||||
*/
|
||||
export async function fileImport(
|
||||
files: File[], app: AppModel, workspaceId: number | "unsaved"): Promise<string | null> {
|
||||
// There is just one file (thanks to {multiple: false} above).
|
||||
const progressUI = app.notifier.createProgressIndicator(files[0].name, byteString(files[0].size));
|
||||
const progress = ImportProgress.create(progressUI, progressUI, files[0]);
|
||||
@@ -110,3 +120,44 @@ export class ImportProgress extends Disposable {
|
||||
this._progressUI.setProgress(100 * progress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports document through a plugin from a home/welcome screen.
|
||||
*/
|
||||
export async function importFromPlugin(
|
||||
app: AppModel,
|
||||
workspaceId: number | "unsaved",
|
||||
importSourceElem: ImportSourceElement
|
||||
) {
|
||||
const screen = PluginScreen.create(null, importSourceElem.importSource.label);
|
||||
try {
|
||||
|
||||
const plugin = importSourceElem.plugin;
|
||||
const handle = screen.renderPlugin(plugin);
|
||||
const importSource = await importSourceElem.importSourceStub.getImportSource(handle);
|
||||
plugin.removeRenderTarget(handle);
|
||||
|
||||
if (importSource) {
|
||||
// If data has been picked, upload it.
|
||||
const item = importSource.item;
|
||||
if (item.kind === "fileList") {
|
||||
const files = item.files.map(({ content, name }) => new File([content], name));
|
||||
const docId = await fileImport(files, app, workspaceId);
|
||||
screen.close();
|
||||
return docId;
|
||||
} else if (item.kind === "url") {
|
||||
//TODO: importing from url is not yet implemented.
|
||||
//uploadResult = await fetchURL(this._docComm, item.url);
|
||||
throw new Error("Url is not supported yet");
|
||||
} else {
|
||||
throw new Error(`Import source of kind ${(item as any).kind} are not yet supported!`);
|
||||
}
|
||||
} else {
|
||||
screen.close();
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
screen.renderError(err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {loadUserManager} from 'app/client/lib/imports';
|
||||
import {ImportSourceElement} from 'app/client/lib/ImportSourceElement';
|
||||
import {reportError} from 'app/client/models/AppModel';
|
||||
import {docUrl, urlState} from 'app/client/models/gristUrlState';
|
||||
import {HomeModel} from 'app/client/models/HomeModel';
|
||||
import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo';
|
||||
import {addNewButton, cssAddNewButton} from 'app/client/ui/AddNewButton';
|
||||
import {docImport} from 'app/client/ui/HomeImports';
|
||||
import {createHelpTools, cssLeftPanel, cssScrollPane, cssSectionHeader, cssTools} from 'app/client/ui/LeftPanelCommon';
|
||||
import {docImport, importFromPlugin} from 'app/client/ui/HomeImports';
|
||||
import {cssLinkText, cssPageEntry, cssPageIcon, cssPageLink} from 'app/client/ui/LeftPanelCommon';
|
||||
import {transientInput} from 'app/client/ui/transientInput';
|
||||
import {colors, testId} from 'app/client/ui2018/cssVars';
|
||||
@@ -14,7 +14,9 @@ import {menu, menuIcon, menuItem, upgradableMenuItem, upgradeText} from 'app/cli
|
||||
import {confirmModal} from 'app/client/ui2018/modals';
|
||||
import * as roles from 'app/common/roles';
|
||||
import {Workspace} from 'app/common/UserAPI';
|
||||
import {computed, dom, DomElementArg, Observable, observable, styled} from 'grainjs';
|
||||
import {computed, dom, domComputed, DomElementArg, observable, Observable, styled} from 'grainjs';
|
||||
import {createHelpTools, cssLeftPanel, cssScrollPane,
|
||||
cssSectionHeader, cssTools} from 'app/client/ui/LeftPanelCommon';
|
||||
|
||||
export function createHomeLeftPane(leftPanelOpen: Observable<boolean>, home: HomeModel) {
|
||||
const creating = observable<boolean>(false);
|
||||
@@ -138,6 +140,23 @@ export async function importDocAndOpen(home: HomeModel) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function importFromPluginAndOpen(home: HomeModel, source: ImportSourceElement) {
|
||||
try {
|
||||
const destWS = home.newDocWorkspace.get();
|
||||
if (!destWS) { return; }
|
||||
const docId = await importFromPlugin(
|
||||
home.app,
|
||||
destWS === "unsaved" ? "unsaved" : destWS.id,
|
||||
source);
|
||||
if (docId) {
|
||||
const doc = await home.app.api.getDoc(docId);
|
||||
await urlState().pushUrl(docUrl(doc));
|
||||
}
|
||||
} catch (err) {
|
||||
reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function addMenu(home: HomeModel, creating: Observable<boolean>): DomElementArg[] {
|
||||
const org = home.app.currentOrg;
|
||||
const orgAccess: roles.Role|null = org ? org.access : null;
|
||||
@@ -152,6 +171,14 @@ function addMenu(home: HomeModel, creating: Observable<boolean>): DomElementArg[
|
||||
dom.cls('disabled', !home.newDocWorkspace.get()),
|
||||
testId("dm-import")
|
||||
),
|
||||
domComputed(home.importSources, importSources => ([
|
||||
...importSources.map((source, i) =>
|
||||
menuItem(() => importFromPluginAndOpen(home, source),
|
||||
menuIcon('Import'),
|
||||
source.importSource.label,
|
||||
testId(`dm-import-plugin`)
|
||||
))
|
||||
])),
|
||||
// For workspaces: if ACL says we can create them, but product says we can't,
|
||||
// then offer an upgrade link.
|
||||
upgradableMenuItem(needUpgrade, () => creating.set(true), menuIcon('Folder'), "Create Workspace",
|
||||
|
||||
Reference in New Issue
Block a user