From 77e08a4362ba8fab4cab361fcb472702c97edf15 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 1 Sep 2019 13:05:54 -0400 Subject: initial commit --- src/server/authentication/config/passport.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'src/server/authentication/config') diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index d42741410..97ded8785 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -1,12 +1,14 @@ import * as passport from 'passport'; import * as passportLocal from 'passport-local'; -import * as mongodb from 'mongodb'; -import * as _ from "lodash"; +import _ from "lodash"; import { default as User } from '../models/user_model'; import { Request, Response, NextFunction } from "express"; import { RouteStore } from '../../RouteStore'; +import * as GoogleOAuth from "passport-google-oauth20"; +const config = require("../../credentials/google_photos_credentials"); const LocalStrategy = passportLocal.Strategy; +const GoogleOAuthStrategy = GoogleOAuth.Strategy; passport.serializeUser((user, done) => { done(undefined, user.id); @@ -32,6 +34,18 @@ passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }); })); + +passport.use(new GoogleOAuthStrategy( + { + clientID: config.oAuthClientID, + clientSecret: config.oAuthclientSecret, + callbackURL: config.oAuthCallbackUrl, + // Set the correct profile URL that does not require any additional APIs + userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo' + }, + (token, refreshToken, profile, done) => done(undefined, { profile, token }) +)); + export let isAuthenticated = (req: Request, res: Response, next: NextFunction) => { if (req.isAuthenticated()) { return next(); -- cgit v1.2.3-70-g09d2 From 5e12b7d816f1778af112ce69f3029e2f4a72bb08 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 1 Sep 2019 14:03:32 -0400 Subject: authentication working --- src/server/apis/google/GoogleApiServerUtils.ts | 25 ++++++++-------- src/server/apis/google/GooglePhotosUtils.ts | 22 +++++++------- src/server/authentication/config/passport.ts | 13 -------- src/server/credentials/auth.json | 12 -------- .../credentials/google_photos_credentials.ts | 35 ---------------------- src/server/index.ts | 11 ++++--- 6 files changed, 29 insertions(+), 89 deletions(-) delete mode 100644 src/server/credentials/auth.json delete mode 100644 src/server/credentials/google_photos_credentials.ts (limited to 'src/server/authentication/config') diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 2fb44d9a2..c1bd3300e 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,7 +1,7 @@ import { google, docs_v1, slides_v1 } from "googleapis"; import { createInterface } from "readline"; import { readFile, writeFile } from "fs"; -import { OAuth2Client } from "google-auth-library"; +import { OAuth2Client, Credentials } from "google-auth-library"; import { Opt } from "../../../new_fields/Doc"; import { GlobalOptions } from "googleapis-common"; import { GaxiosResponse } from "gaxios"; @@ -48,14 +48,14 @@ export namespace GoogleApiServerUtils { export const GetEndpoint = async (sector: string, paths: CredentialPaths) => { return new Promise>((resolve, reject) => { - readFile(paths.credentials, (err, credentials) => { + readFile(paths.credentials, async (err, credentials) => { if (err) { reject(err); return console.log('Error loading client secret file:', err); } - return authorize(parseBuffer(credentials), paths.token).then(async auth => { + authorize(parseBuffer(credentials), paths.token).then(async result => { let routed: Opt; - let parameters: EndpointParameters = { auth, version: "v1" }; + let parameters: EndpointParameters = { auth: result.client, version: "v1" }; switch (sector) { case Service.Documents: routed = google.docs(parameters).documents; @@ -64,7 +64,7 @@ export namespace GoogleApiServerUtils { routed = google.slides(parameters).presentations; break; case Service.Photos: - const photos = new Photos(auth); + const photos = new Photos(result.token.access_token); let response = await photos.albums.list(); console.log("WE GOT SOMETHING!", response); } @@ -74,24 +74,25 @@ export namespace GoogleApiServerUtils { }); }; - + type TokenResult = { token: Credentials, client: OAuth2Client }; /** * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client * @param {Object} credentials The authorization client credentials. */ - export function authorize(credentials: any, token_path: string): Promise { + export function authorize(credentials: any, token_path: string): Promise { const { client_secret, client_id, redirect_uris } = credentials.installed; const oAuth2Client = new google.auth.OAuth2( client_id, client_secret, redirect_uris[0]); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { readFile(token_path, (err, token) => { // Check if we have previously stored a token. if (err) { return getNewToken(oAuth2Client, token_path).then(resolve, reject); } - oAuth2Client.setCredentials(parseBuffer(token)); - resolve(oAuth2Client); + let parsed = parseBuffer(token); + oAuth2Client.setCredentials(parsed); + resolve({ token: parsed, client: oAuth2Client }); }); }); } @@ -103,7 +104,7 @@ export namespace GoogleApiServerUtils { * @param {getEventsCallback} callback The callback for the authorized client. */ function getNewToken(oAuth2Client: OAuth2Client, token_path: string) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const authUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES.map(relative => prefix + relative), @@ -129,7 +130,7 @@ export namespace GoogleApiServerUtils { } console.log('Token stored to', token_path); }); - resolve(oAuth2Client); + resolve({ token, client: oAuth2Client }); }); }); }); diff --git a/src/server/apis/google/GooglePhotosUtils.ts b/src/server/apis/google/GooglePhotosUtils.ts index c33ad2dd9..7f9ffb6f3 100644 --- a/src/server/apis/google/GooglePhotosUtils.ts +++ b/src/server/apis/google/GooglePhotosUtils.ts @@ -1,12 +1,12 @@ -import request = require('request-promise'); -const key = require("../../credentials/auth.json"); +// import request = require('request-promise'); +// const key = require("../../credentials/auth.json"); -export const PhotosLibraryQuery = async (authToken: any, parameters: any) => { - let options = { - headers: { 'Content-Type': 'application/json' }, - json: parameters, - auth: { 'bearer': authToken }, - }; - const result = await request.post(config.apiEndpoint + '/v1/mediaItems:search', options); - return result; -}; \ No newline at end of file +// export const PhotosLibraryQuery = async (authToken: any, parameters: any) => { +// let options = { +// headers: { 'Content-Type': 'application/json' }, +// json: parameters, +// auth: { 'bearer': authToken }, +// }; +// const result = await request.post(config.apiEndpoint + '/v1/mediaItems:search', options); +// return result; +// }; \ No newline at end of file diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index 97ded8785..6e0e01b9e 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -5,7 +5,6 @@ import { default as User } from '../models/user_model'; import { Request, Response, NextFunction } from "express"; import { RouteStore } from '../../RouteStore'; import * as GoogleOAuth from "passport-google-oauth20"; -const config = require("../../credentials/google_photos_credentials"); const LocalStrategy = passportLocal.Strategy; const GoogleOAuthStrategy = GoogleOAuth.Strategy; @@ -34,18 +33,6 @@ passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }); })); - -passport.use(new GoogleOAuthStrategy( - { - clientID: config.oAuthClientID, - clientSecret: config.oAuthclientSecret, - callbackURL: config.oAuthCallbackUrl, - // Set the correct profile URL that does not require any additional APIs - userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo' - }, - (token, refreshToken, profile, done) => done(undefined, { profile, token }) -)); - export let isAuthenticated = (req: Request, res: Response, next: NextFunction) => { if (req.isAuthenticated()) { return next(); diff --git a/src/server/credentials/auth.json b/src/server/credentials/auth.json deleted file mode 100644 index 557eca4b6..000000000 --- a/src/server/credentials/auth.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "brown-dash", - "private_key_id": "ddf0473a9ac56956b5818e04a7ee406a64d5b0a6", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCueRfxic2oL9nr\nnWSLgl7XR/BKikm4p2sib6szaoTO+q6itcJgt2TDleK/7Y4KW/KhvCfhWVet0Hz0\nIDyg4N/gc2yxuDA6/m8DPWU9kDj8VFR7LVFawOKgo1WbgLcC0Qu8qHzAffrlg8si\nhj3vGuoS/YDn/mz0krwFmCfIx+S0lJ9a7FUjJL5C+CIwAEEYiU7xnTW7pVVNXAm/\n/YKD17ToAjREOtlfVVYO7tZ7V5BiW0I0jJvxw+t1pgrZZe7WPBSBJg9KKGIl+mRi\ndtUMR9Hyt3nMKNZIrSm0OkAz82HxfcapRdSB3wkVjoyW63YaTVHKoBOqRElfMtoM\nqu8wbhhNAgMBAAECggEAA41wJ8kg8J8peQMZ/b7gZvzuPy+h0M/J3j2MrG3rY9qA\nrUv1oqoBSXvhuNDhtEN12oYIqtg6m+L+Sas8CMuOC2rWPafM2u/80IGoGtDhtCjp\nv8inBX8ew4YSiL7IxdbTU2/70Es7DVV5u0t6ndsmr88ibYwwPupGR/fhPCpDyssg\n7lFAEpOwnbKG9a7E7axHpXBRSIE54sh+ESyf6MHH/oyKOLhZ0v4PjRDKaKuMDRst\nMOClgNjD/4bzKpfWljuPYemXz1oIBQitBW5aXnCdsmdrmOLDQpz3qOgIo+RRiyki\nvVU5N54L65sj4WisLt1TT45wbhrkQUz+8GmhV5rHwQKBgQDow32/gSb/M0BKFk5y\n+pSeoLkYp/dwfBFWYT6CNBaKePARFVCdr3db8yOEQD4hrmTOU0EP9c4FSLcaa0Xy\n7n2crhhfZWFpIpRMyXhpeKpqdjQFimfBOK6cIdjnWrtJ6Ik3m4E9p7kKThIsInTc\n/TETwAyzFN+J2ADRdrUdCukOwQKBgQC/4+1Rk8a++Jr9Sznx+JH4vj/J2cGsu7uQ\n0nikcOAFO5HzG7+mt9Wv9/MiPtEYwmc7YziDXldKLpshT2m6wrS1uzzOXAnvXFAh\npiCXQsmVA3gmrVd53k+eZfqrZ1n/rL1kCewRS5LX8xIhM28VIkGqkVy4ZEifMotG\nZKSbH0b4jQKBgQCsJ6rh8Uw+hFGQel8be2pgyM8eBV1lvN213ca11oC1ei1U9Ubi\n2dyWDYa/UiSiFLJKSBlfDJaMIfQLfjwGKY6OS9WK+RjLAeBdysVcfPrOMw7W6j9D\nEgFTSVV8CAdt6qdSkZlNWLfrf0LBkdqNeFbMHMdHzLBo63HverUJ/f/SAQKBgHIk\n2t5T0T14FHnnbaiJ/ArC4J7pcVOWuJQFHs5ydk+mh8LdFrvNTsdF7tLIGwlnWpDx\nDITYcYQnBRBjdLkraONRZXI7PY2sk93wPCK+D7scPTSEmCxeGW5XqyyaZea4klAX\nttzy336lkHs/ZSxlHDqiDU2CGdDY+A//fgroKAdhAoGAA5FXfMzTQLGqxg4J2B1z\nFEXNbrqZZFGgKiveUhhZLm4zPiHXtZXvDtSLwGgcO8oGfTfYueTcHb/Eiar7mKv+\n+SqpAqkINJTthIFVIiD39S9jPFUXzBkf5ZJKPLKQArhzEGxen+SD6ZUO058fA94L\n9FblRGlMtr2o5z0NC7H5zaU=\n-----END PRIVATE KEY-----\n", - "client_email": "google-photos-api@brown-dash.iam.gserviceaccount.com", - "client_id": "112995422877175743408", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-photos-api%40brown-dash.iam.gserviceaccount.com" -} \ No newline at end of file diff --git a/src/server/credentials/google_photos_credentials.ts b/src/server/credentials/google_photos_credentials.ts deleted file mode 100644 index 11c1c766c..000000000 --- a/src/server/credentials/google_photos_credentials.ts +++ /dev/null @@ -1,35 +0,0 @@ -const config: any = {}; - -// The OAuth client ID from the Google Developers console. -config.oAuthClientID = '1005546247619-l40012sl4idpq17b5emielcs1delffog.apps.googleusercontent.com'; - -// The OAuth client secret from the Google Developers console. -config.oAuthclientSecret = 'xEUJ0OBvhlCKA6SLt8TvWBs3'; - -// The callback to use for OAuth requests. This is the URL where the app is -// running. For testing and running it locally, use 127.0.0.1. -config.oAuthCallbackUrl = 'http://localhost:1050/auth/google/callback'; - -// The port where the app should listen for requests. -config.port = 1050; - -// The scopes to request. The app requires the photoslibrary.readonly and -// plus.me scopes. -config.scopes = [ - 'https://www.googleapis.com/auth/photoslibrary.readonly', - 'profile', -]; - -// The number of photos to load for search requests. -config.photosToLoad = 150; - -// The page size to use for search requests. 100 is reccommended. -config.searchPageSize = 100; - -// The page size to use for the listing albums request. 50 is reccommended. -config.albumPageSize = 50; - -// The API end point to use. Do not change. -config.apiEndpoint = 'https://photoslibrary.googleapis.com'; - -module.exports = config; \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 6105dedcc..1f105e9d2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -29,7 +29,6 @@ import { RouteStore } from './RouteStore'; import v4 = require('uuid/v4'); const app = express(); const config = require('../../webpack.config'); -const OAuthConfig = require('../server/credentials/google_photos_credentials'); import { createCanvas, loadImage, Canvas } from "canvas"; const compiler = webpack(config); const port = 1050; // default port to listen @@ -43,11 +42,11 @@ var AdmZip = require('adm-zip'); import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; -import { GaxiosResponse } from 'gaxios'; -import { Opt } from '../new_fields/Doc'; -import { docs_v1 } from 'googleapis'; -import { Endpoint } from 'googleapis-common'; -import { PhotosLibraryQuery } from './apis/google/GooglePhotosUtils'; +// import { GaxiosResponse } from 'gaxios'; +// import { Opt } from '../new_fields/Doc'; +// import { docs_v1 } from 'googleapis'; +// import { Endpoint } from 'googleapis-common'; +// import { PhotosLibraryQuery } from './apis/google/GooglePhotosUtils'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); -- cgit v1.2.3-70-g09d2 From 769b4c0b9ac61729b94b32999d3713a2dce53627 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 1 Sep 2019 17:02:11 -0400 Subject: added typings for google photo album manipulations --- src/server/apis/google/GoogleApiServerUtils.ts | 17 ++- src/server/apis/google/GooglePhotosUtils.ts | 177 +++++++++++++++++++++++-- src/server/authentication/config/passport.ts | 2 - src/server/credentials/google_docs_token.json | 2 +- 4 files changed, 180 insertions(+), 18 deletions(-) (limited to 'src/server/authentication/config') diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index bc9ae2726..656984b8a 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -5,7 +5,8 @@ import { OAuth2Client, Credentials } from "google-auth-library"; import { Opt } from "../../../new_fields/Doc"; import { GlobalOptions } from "googleapis-common"; import { GaxiosResponse } from "gaxios"; -import Photos = require("googlephotos"); +import { GooglePhotos, CreateAlbum, Action } from "./GooglePhotosUtils"; +import { Utils } from "../../../Utils"; /** * Server side authentication for Google Api queries. @@ -64,8 +65,18 @@ export namespace GoogleApiServerUtils { routed = google.slides(parameters).presentations; break; case Service.Photos: - const photos = new Photos(result.token.access_token); - console.log(await photos.albums.list()); + let token = result.token.access_token; + if (token) { + let create: CreateAlbum = { + action: Action.Create, + body: { + album: { + title: "Sam's Bulk Export", + } + } + }; + console.log(await GooglePhotos.ExecuteQuery(token, create)); + } } resolve(routed); }); diff --git a/src/server/apis/google/GooglePhotosUtils.ts b/src/server/apis/google/GooglePhotosUtils.ts index 7f9ffb6f3..439a41cb6 100644 --- a/src/server/apis/google/GooglePhotosUtils.ts +++ b/src/server/apis/google/GooglePhotosUtils.ts @@ -1,12 +1,165 @@ -// import request = require('request-promise'); -// const key = require("../../credentials/auth.json"); - -// export const PhotosLibraryQuery = async (authToken: any, parameters: any) => { -// let options = { -// headers: { 'Content-Type': 'application/json' }, -// json: parameters, -// auth: { 'bearer': authToken }, -// }; -// const result = await request.post(config.apiEndpoint + '/v1/mediaItems:search', options); -// return result; -// }; \ No newline at end of file +import request = require('request-promise'); + +const apiEndpoint = "https://photoslibrary.googleapis.com"; + +export type GooglePhotosQuery = AlbumsQuery; + +export type AlbumsQuery = (AddEnrichment | BatchAddMediaItems | BatchRemoveMediaItems | CreateAlbum | GetAlbum | ListAlbum | ShareAlbum | UnshareAlbum) & { body: any }; + +export enum Action { + AddEnrichment, + BatchAddMediaItems, + BatchRemoveMediaItems, + Create, + Get, + List, + Share, + Unshare +} + +export interface AddEnrichment { + action: Action.AddEnrichment; + albumId: string; + body: { + newEnrichmentItem: NewEnrichmentItem; + albumPosition: MediaRelativeAlbumPosition; + }; +} + +export interface BatchAddMediaItems { + action: Action.BatchAddMediaItems; + albumId: string; + body: { + mediaItemIds: string[]; + }; +} + +export interface BatchRemoveMediaItems { + action: Action.BatchRemoveMediaItems; + albumId: string; + body: { + mediaItemIds: string[]; + }; +} + +export interface CreateAlbum { + action: Action.Create; + body: { + album: AlbumTemplate; + }; +} + +export interface GetAlbum { + action: Action.Get; + albumId: string; +} + +export interface ListAlbum { + action: Action.List; + parameters: { + pageSize: number, + pageToken: string, + excludeNonAppCreatedData: boolean + }; +} + +export interface ShareAlbum { + action: Action.Share; + albumId: string; + body: { + sharedAlbumOptions: SharedAlbumOptions; + }; +} + +export interface UnshareAlbum { + action: Action.Unshare; + albumId: string; +} + +export interface AlbumTemplate { + title: string; +} + +export interface Album { + id: string; + title: string; + productUrl: string; + isWriteable: boolean; + shareInfo: ShareInfo; + mediaItemsCount: string; + coverPhotoBaseUrl: string; + coverPhotoMediaItemId: string; +} + +export interface ShareInfo { + sharedAlbumOptions: SharedAlbumOptions; + shareableUrl: string; + shareToken: string; + isJoined: boolean; + isOwned: boolean; +} + +export interface SharedAlbumOptions { + isCollaborative: boolean; + isCommentable: boolean; +} + +export enum PositionType { + POSITION_TYPE_UNSPECIFIED, + FIRST_IN_ALBUM, + LAST_IN_ALBUM, + AFTER_MEDIA_ITEM, + AFTER_ENRICHMENT_ITEM +} + +export type AlbumPosition = GeneralAlbumPosition | MediaRelativeAlbumPosition | EnrichmentRelativeAlbumPosition; + +interface GeneralAlbumPosition { + position: PositionType.FIRST_IN_ALBUM | PositionType.LAST_IN_ALBUM | PositionType.POSITION_TYPE_UNSPECIFIED; +} + +interface MediaRelativeAlbumPosition { + position: PositionType.AFTER_MEDIA_ITEM; + relativeMediaItemId: string; +} + +interface EnrichmentRelativeAlbumPosition { + position: PositionType.AFTER_ENRICHMENT_ITEM; + relativeEnrichmentItemId: string; +} + +export interface Location { + locationName: string; + latlng: { + latitude: number, + longitude: number + }; +} + +export interface NewEnrichmentItem { + textEnrichment: { + text: string; + }; + locationEnrichment: { + location: Location + }; + mapEnrichment: { + origin: { location: Location }, + destination: { location: Location } + }; +} + +export namespace GooglePhotos { + + export const ExecuteQuery = async (authToken: string, query: AlbumsQuery) => { + let options = { + headers: { 'Content-Type': 'application/json' }, + auth: { 'bearer': authToken }, + body: query.body, + json: true + }; + const result = await request.post(apiEndpoint + '/v1/albums', options); + return result; + }; + +} diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index 6e0e01b9e..e5733cbb5 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -4,10 +4,8 @@ import _ from "lodash"; import { default as User } from '../models/user_model'; import { Request, Response, NextFunction } from "express"; import { RouteStore } from '../../RouteStore'; -import * as GoogleOAuth from "passport-google-oauth20"; const LocalStrategy = passportLocal.Strategy; -const GoogleOAuthStrategy = GoogleOAuth.Strategy; passport.serializeUser((user, done) => { done(undefined, user.id); diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json index fcb5c8abc..61864512c 100644 --- a/src/server/credentials/google_docs_token.json +++ b/src/server/credentials/google_docs_token.json @@ -1 +1 @@ -{"access_token":"ya29.Glt2ByOBCyO7DNKKXaiDbK5c5OMwRoprqiCksLCu93WKuAE-YQ0gDCZQCqP07WV6QH0gMpwn47ghico1h5Rkxh-ukJdY9ndRTv7rEKJY32To4__gZh4xKVhwfOvf","refresh_token":"1/ynmFZmA-yqA1sKU3wF-g6QxCx9wGSTIA2sOvIhC_Ps0","scope":"https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.sharing https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/documents.readonly","token_type":"Bearer","expiry_date":1567366717827} \ No newline at end of file +{"access_token":"ya29.Glt3B8HoVEda7Ab5TQMVrfvjPN2fFp4sFHtGoDs3TsBgFfw4G208q90JiFjkmQqwODjJi3sf4NCZd78VZTVL3aI0By7_ElZF7XaCvA0LJnfcAi2gi1P-2-boyjYO","refresh_token":"1/tJOVDbPZlADzd2B8Q2_j7jqignXlRwHsU7LbZkdbDBc","scope":"https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/photoslibrary.sharing https://www.googleapis.com/auth/photoslibrary","token_type":"Bearer","expiry_date":1567374969108} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From f82458be8bc8beaab387cc2813b7b18c9b3caac2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 22 Sep 2019 17:16:18 -0400 Subject: initial commit post master merge --- src/Utils.ts | 21 +- .../apis/google_docs/GooglePhotosClientUtils.ts | 7 +- src/client/documents/Documents.ts | 51 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DocumentManager.ts | 44 +- src/client/util/DragManager.ts | 44 +- src/client/util/History.ts | 4 +- .../util/Import & Export/DirectoryImportBox.tsx | 1 + src/client/util/ProsemirrorExampleTransfer.ts | 4 +- src/client/util/RichTextRules.ts | 24 +- src/client/util/RichTextSchema.tsx | 22 +- src/client/util/Scripting.ts | 8 +- src/client/util/SelectionManager.ts | 4 + src/client/util/SharingManager.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 20 +- src/client/util/UndoManager.ts | 2 +- src/client/views/ContextMenu.tsx | 2 +- src/client/views/DocumentButtonBar.scss | 129 ++++ src/client/views/DocumentButtonBar.tsx | 368 ++++++++++ src/client/views/DocumentDecorations.scss | 2 +- src/client/views/DocumentDecorations.tsx | 425 ++--------- src/client/views/GlobalKeyHandler.ts | 11 +- src/client/views/InkingCanvas.scss | 1 + src/client/views/InkingControl.tsx | 34 +- src/client/views/MainOverlayTextBox.tsx | 23 +- src/client/views/MainView.tsx | 80 +- src/client/views/OverlayView.tsx | 4 +- src/client/views/ScriptBox.tsx | 2 +- src/client/views/ScriptingRepl.tsx | 20 +- src/client/views/TemplateMenu.tsx | 57 +- .../views/collections/CollectionBaseView.tsx | 24 +- .../views/collections/CollectionDockingView.tsx | 130 ++-- .../views/collections/CollectionSchemaCells.tsx | 17 +- .../CollectionSchemaMovableTableHOC.tsx | 8 +- .../views/collections/CollectionSchemaView.tsx | 22 +- .../views/collections/CollectionStackingView.tsx | 9 +- .../CollectionStackingViewFieldColumn.tsx | 13 +- src/client/views/collections/CollectionSubView.tsx | 67 +- .../views/collections/CollectionTreeView.tsx | 36 +- src/client/views/collections/CollectionView.tsx | 6 + .../views/collections/CollectionViewChromes.tsx | 17 +- .../views/collections/ParentDocumentSelector.scss | 9 + .../views/collections/ParentDocumentSelector.tsx | 46 +- .../CollectionFreeFormLinkView.scss | 3 +- .../CollectionFreeFormLinkView.tsx | 1 - .../CollectionFreeFormLinksView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 474 +++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 78 +- .../document_templates/image_card/ImageCard.tsx | 3 - src/client/views/linking/LinkFollowBox.tsx | 48 +- src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/linking/LinkMenuGroup.tsx | 26 +- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 6 +- .../nodes/CollectionFreeFormDocumentView.scss | 5 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 95 ++- src/client/views/nodes/DocumentContentsView.tsx | 9 +- src/client/views/nodes/DocumentView.scss | 42 ++ src/client/views/nodes/DocumentView.tsx | 810 ++++++++------------- src/client/views/nodes/DragBox.tsx | 10 +- src/client/views/nodes/FieldView.tsx | 4 +- src/client/views/nodes/FormattedTextBox.scss | 1 - src/client/views/nodes/FormattedTextBox.tsx | 165 +++-- src/client/views/nodes/IconBox.tsx | 52 +- src/client/views/nodes/ImageBox.tsx | 55 +- src/client/views/nodes/KeyValueBox.tsx | 6 +- src/client/views/nodes/KeyValuePair.tsx | 9 +- src/client/views/nodes/PDFBox.tsx | 106 ++- src/client/views/nodes/VideoBox.tsx | 31 +- src/client/views/pdf/Annotation.tsx | 10 +- src/client/views/pdf/PDFAnnotationLayer.scss | 6 - src/client/views/pdf/PDFAnnotationLayer.tsx | 21 - src/client/views/pdf/PDFViewer.tsx | 89 +-- src/client/views/pdf/Page.scss | 5 + src/client/views/pdf/Page.tsx | 2 +- .../views/presentationview/PresentationElement.tsx | 2 + .../views/presentationview/PresentationList.tsx | 4 +- src/client/views/search/FilterBox.tsx | 6 +- src/client/views/search/SearchItem.tsx | 8 +- src/debug/Repl.tsx | 8 +- src/new_fields/Doc.ts | 176 +++-- src/new_fields/RichTextUtils.ts | 4 +- src/new_fields/ScriptField.ts | 36 +- src/server/authentication/config/passport.ts | 2 +- .../authentication/models/current_user_utils.ts | 40 +- 85 files changed, 2212 insertions(+), 2078 deletions(-) create mode 100644 src/client/views/DocumentButtonBar.scss create mode 100644 src/client/views/DocumentButtonBar.tsx create mode 100644 src/client/views/nodes/CollectionFreeFormDocumentView.scss delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.scss delete mode 100644 src/client/views/pdf/PDFAnnotationLayer.tsx (limited to 'src/server/authentication/config') diff --git a/src/Utils.ts b/src/Utils.ts index a842f5a20..6489eff77 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -123,28 +123,23 @@ export class Utils { // Calculate hue // No difference - if (delta == 0) - h = 0; + if (delta === 0) h = 0; // Red is max - else if (cmax == r) - h = ((g - b) / delta) % 6; + else if (cmax === r) h = ((g - b) / delta) % 6; // Green is max - else if (cmax == g) - h = (b - r) / delta + 2; + else if (cmax === g) h = (b - r) / delta + 2; // Blue is max - else - h = (r - g) / delta + 4; + else h = (r - g) / delta + 4; h = Math.round(h * 60); // Make negative hues positive behind 360° - if (h < 0) - h += 360; // Calculate lightness + if (h < 0) h += 360; // Calculate lightness l = (cmax + cmin) / 2; // Calculate saturation - s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); // Multiply l and s by 100 // s = +(s * 100).toFixed(1); @@ -248,6 +243,10 @@ export function timenow() { return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; } +export function percent2frac(percent: string) { + return Number(percent.substr(0, percent.length - 1)) / 100; +} + export function numberRange(num: number) { return Array.from(Array(num)).map((v, i) => i); } export function returnTrue() { return true; } diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index 671d05421..559b8fd6a 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -15,6 +15,7 @@ import { AssertionError } from "assert"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { DocumentView } from "../../views/nodes/DocumentView"; +import { DocumentManager } from "../../util/DocumentManager"; export namespace GooglePhotos { @@ -140,6 +141,8 @@ export namespace GooglePhotos { export namespace Query { const delimiter = ", "; + const comparator = (a: string, b: string) => (a < b) ? -1 : (a > b ? 1 : 0); + export const TagChildImages = async (collection: Doc) => { const idMapping = await Cast(collection.googlePhotosIdMapping, Doc); if (!idMapping) { @@ -172,7 +175,7 @@ export namespace GooglePhotos { const tags = concatenated.split(delimiter); if (tags.length > 1) { const cleaned = concatenated.replace(ContentCategories.NONE + delimiter, ""); - image.googlePhotosTags = cleaned.split(delimiter).sort((a, b) => (a < b) ? -1 : (a > b ? 1 : 0)).join(delimiter); + image.googlePhotosTags = cleaned.split(delimiter).sort(comparator).join(delimiter); } else { image.googlePhotosTags = ContentCategories.NONE; } @@ -326,7 +329,7 @@ export namespace GooglePhotos { const url = data.url.href; const target = Doc.MakeAlias(source); const description = parseDescription(target, descriptionKey); - DocumentView.makeCustomViewClicked(target); + DocumentView.makeCustomViewClicked(target, undefined); media.push({ url, description }); }); if (media.length) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cfed2bf14..a7a006f47 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -20,8 +20,8 @@ import { AttributeTransformationModel } from "../northstar/core/attribute/Attrib import { AggregateFunction } from "../northstar/model/idea/idea"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { IconBox } from "../views/nodes/IconBox"; -import { Field, Doc, Opt } from "../../new_fields/Doc"; -import { OmitKeys, JSONUtils, Utils } from "../../Utils"; +import { OmitKeys, JSONUtils } from "../../Utils"; +import { Field, Doc, Opt, DocListCastAsync } from "../../new_fields/Doc"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; @@ -65,7 +65,7 @@ export interface DocumentOptions { panY?: number; page?: number; scale?: number; - layout?: string; + layout?: string | Doc; isTemplate?: boolean; templates?: List; viewType?: number; @@ -121,7 +121,7 @@ export namespace Docs { }], [DocumentType.IMG, { layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, - options: { nativeWidth: 600, curPage: 0 } + options: { curPage: 0 } }], [DocumentType.WEB, { layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, @@ -137,7 +137,7 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType }, - options: { nativeWidth: 600, curPage: 0 }, + options: { curPage: 0 }, }], [DocumentType.AUDIO, { layout: { view: AudioBox }, @@ -614,10 +614,40 @@ export namespace Docs { export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string) { + export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { + targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + DocServer.GetRefField(targetID).then(doc => { + if (promoteDoc !== doc) { + let copy = doc as Doc; + if (copy) { + Doc.Overwrite(promoteDoc, copy, true); + } else { + copy = Doc.MakeCopy(promoteDoc, true, targetID); + } + !doc && (copy.title = undefined) && (Doc.GetProto(copy).title = targetID); + addDoc && addDoc(copy); + remDoc && remDoc(promoteDoc); + if (!doc) { + DocListCastAsync(promoteDoc.links).then(links => { + links && links.map(async link => { + if (link) { + let a1 = await Cast(link.anchor1, Doc); + if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; + let a2 = await Cast(link.anchor2, Doc); + if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; + LinkManager.Instance.deleteLink(link); + LinkManager.Instance.addLink(link); + } + }); + }); + } + } + }); + } + export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) { if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); - if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; + if (sv && sv.props.ContainingCollectionDoc === target) return; if (target === CurrentUserUtils.UserDocument) return undefined; let linkDocProto = new Doc(id, true); @@ -633,16 +663,15 @@ export namespace DocUtils { linkDocProto.anchor1 = source; linkDocProto.anchor1Page = source.curPage; linkDocProto.anchor1Groups = new List([]); + linkDocProto.anchor1anchored = anchored1; linkDocProto.anchor2 = target; linkDocProto.anchor2Page = target.curPage; linkDocProto.anchor2Groups = new List([]); LinkManager.Instance.addLink(linkDocProto); - let script = `return links(this);`; - let computed = CompileScript(script, { params: { this: "Doc" }, typecheck: false }); - computed.compiled && (Doc.GetProto(source).links = new ComputedField(computed)); - computed.compiled && (Doc.GetProto(target).links = new ComputedField(computed)); + Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)"); }, "make link"); return linkDocProto; } diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 0711effe6..cebb56bbe 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -327,7 +327,7 @@ export namespace DictationManager { ["open fields", { action: (target: DocumentView) => { let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 }); - target.props.addDocTab(kvp, target.dataDoc, "onRight"); + target.props.addDocTab(kvp, target.props.DataDoc, "onRight"); } }], diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ec731da84..a3c7429b9 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,6 +10,7 @@ import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { undoBatch, UndoManager } from './UndoManager'; import { Scripting } from './Scripting'; +import { List } from '../../new_fields/List'; export class DocumentManager { @@ -146,6 +147,7 @@ export class DocumentManager { if (!contextDoc) { let docs = docContext ? await DocListCastAsync(docContext.data) : undefined; let found = false; + // bcz: this just searches within the context for the target -- perhaps it should recursively search through all children? docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate)); if (docContext && found) { let targetContextView: DocumentView | null; @@ -154,16 +156,19 @@ export class DocumentManager { docContext.panTransformType = "Ease"; targetContextView.props.focus(docDelegate, willZoom); } else { - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined); setTimeout(() => { - this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage); - }, 10); + let dv = DocumentManager.Instance.getDocumentView(docContext); + dv && this.jumpToDocument(docDelegate, willZoom, forceDockFunc, + doc => dv!.props.focus(dv!.props.Document, true, 1, () => dv!.props.addDocTab(doc, undefined, "inPlace")), + linkPage); + }, 1050); } } else { const actualDoc = Doc.MakeAlias(docDelegate); Doc.BrushDoc(actualDoc); if (linkPage !== undefined) actualDoc.curPage = linkPage; - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc, undefined); } } else { let contextView: DocumentView | null; @@ -172,7 +177,7 @@ export class DocumentManager { contextDoc.panTransformType = "Ease"; contextView.props.focus(docDelegate, willZoom); } else { - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, undefined); + (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined); setTimeout(() => { this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage); }, 10); @@ -203,5 +208,34 @@ export class DocumentManager { return 1; } } + + @action + animateBetweenPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { + expandedDocs && expandedDocs.map(expDoc => { + if (expDoc.isMinimized || expDoc.isAnimating === "min") { // MAXIMIZE DOC + if (expDoc.isMinimized) { // docs are never actaully at the minimized location. so when we unminimize one, we have to set our overrides to make it look like it was at the minimize location + expDoc.isMinimized = false; + expDoc.animateToPos = new List([...scrpt, 0]); + expDoc.animateToDimensions = new List([0, 0]); + } + setTimeout(() => { + expDoc.isAnimating = "max"; + expDoc.animateToPos = new List([0, 0, 1]); + expDoc.animateToDimensions = new List([NumCast(expDoc.width), NumCast(expDoc.height)]); + setTimeout(() => expDoc.isAnimating === "max" && (expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined), 600); + }, 0); + } else { // MINIMIZE DOC + expDoc.isAnimating = "min"; + expDoc.animateToPos = new List([...scrpt, 0]); + expDoc.animateToDimensions = new List([0, 0]); + setTimeout(() => { + if (expDoc.isAnimating === "min") { + expDoc.isMinimized = true; + expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined; + } + }, 600); + } + }); + } } Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4c9c9c17c..56496c99b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -32,7 +32,7 @@ export function SetupDrag( document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); let doc = await docFunc(); - var dragData = new DragManager.DocumentDragData([doc], [undefined]); + var dragData = new DragManager.DocumentDragData([doc]); dragData.dropAction = dropAction; dragData.moveDocument = moveFunc; dragData.options = options; @@ -66,7 +66,7 @@ export function SetupDrag( function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { const document = SelectionManager.SelectedDocuments()[0]; - document.props.removeDocument && document.props.removeDocument(doc); + document && document.props.removeDocument && document.props.removeDocument(doc); addDocument(doc); return true; } @@ -76,7 +76,7 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num if (draggeddoc) { let moddrag = await Cast(draggeddoc.annotationOn, Doc); let dragdocs = moddrag ? [moddrag] : [draggeddoc]; - let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs); + let dragData = new DragManager.DocumentDragData(dragdocs); dragData.moveDocument = moveLinkedDocument; DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { handlers: { @@ -107,7 +107,7 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n if (doc) moddrag.push(doc); } let dragdocs = moddrag.length ? moddrag : draggedDocs; - let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs); + let dragData = new DragManager.DocumentDragData(dragdocs); dragData.moveDocument = moveLinkedDocument; DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { handlers: { @@ -201,18 +201,14 @@ export namespace DragManager { export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; export class DocumentDragData { - constructor(dragDoc: Doc[], dragDataDocs: (Doc | undefined)[]) { + constructor(dragDoc: Doc[]) { this.draggedDocuments = dragDoc; - this.draggedDataDocs = dragDataDocs; this.droppedDocuments = dragDoc; - this.xOffset = 0; - this.yOffset = 0; + this.offset = [0, 0]; } draggedDocuments: Doc[]; - draggedDataDocs: (Doc | undefined)[]; droppedDocuments: Doc[]; - xOffset: number; - yOffset: number; + offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; moveDocument?: MoveFunction; @@ -225,14 +221,13 @@ export namespace DragManager { this.dragDocument = dragDoc; this.dropDocument = dropDoc; this.annotationDocument = annotationDoc; - this.xOffset = this.yOffset = 0; + this.offset = [0, 0]; } targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; dropDocument: Doc; - xOffset: number; - yOffset: number; + offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; } @@ -252,21 +247,13 @@ export namespace DragManager { }); } - export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize?: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { - let dragData = new DragManager.DocumentDragData([], [undefined]); + export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { + let dragData = new DragManager.DocumentDragData([]); runInAction(() => StartDragFunctions.map(func => func())); StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); - let compiled = CompileScript(script, { - params: { doc: Doc.name }, - typecheck: false, - editable: true - }); - if (compiled.compiled) { - let scriptField = new ScriptField(compiled); - bd.onClick = scriptField; - } + bd.onClick = ScriptField.MakeScript(script); params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); initialize && initialize(bd); bd.buttonParams = new List(params); @@ -283,7 +270,8 @@ export namespace DragManager { let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => { let dvs = DocumentManager.Instance.getDocumentViews(d); if (dvs.length) { - let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView); + let containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined; + let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView); if (inContext.length) { inContext.forEach(dv => droppedDocs.push(dv.props.Document)); } else { @@ -363,8 +351,6 @@ export namespace DragManager { const docs: Doc[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : []; - const datadocs: (Doc | undefined)[] = - dragData instanceof DocumentDragData ? dragData.draggedDataDocs : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : []; let dragElements = eles.map(ele => { const w = ele.offsetWidth, h = ele.offsetHeight; @@ -449,7 +435,7 @@ export namespace DragManager { pageY: e.pageY, preventDefault: emptyFunction, button: 0 - }, docs, datadocs); + }, docs); } //TODO: Why can't we use e.movementX and e.movementY? let moveX = e.pageX - lastX; diff --git a/src/client/util/History.ts b/src/client/util/History.ts index c72ae05de..899abbe40 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -54,7 +54,9 @@ export namespace HistoryUtil { } export function getState(): ParsedUrl { - return copyState(history.state); + let state = copyState(history.state); + state.initializers = state.initializers || {}; + return state; } // export function addHandler(handler: (state: ParsedUrl | null) => void) { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 8948b73f7..6670f685e 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -279,6 +279,7 @@ export default class DirectoryImportBox extends React.Component }} />