aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts126
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx7
-rw-r--r--src/client/views/MainView.tsx19
-rw-r--r--src/client/views/nodes/DocumentView.tsx3
-rw-r--r--src/new_fields/RichTextUtils.ts2
-rw-r--r--src/server/apis/google/CustomizedWrapper/filters.js46
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts34
-rw-r--r--src/server/credentials/google_docs_token.json2
-rw-r--r--src/server/index.ts2
9 files changed, 168 insertions, 73 deletions
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index b1de24d1a..a28b183d1 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -10,7 +10,10 @@ import { RichTextUtils } from "../../../new_fields/RichTextUtils";
import { EditorState } from "prosemirror-state";
import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
import { Docs, DocumentOptions } from "../../documents/Documents";
-import { MediaItemCreationResult, NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
+import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
+import { AssertionError } from "assert";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
export namespace GooglePhotos {
@@ -53,7 +56,14 @@ export namespace GooglePhotos {
PERFORMANCES: 'PERFORMANCES',
WHITEBOARDS: 'WHITEBOARDS',
SCREENSHOTS: 'SCREENSHOTS',
- UTILITY: 'UTILITY'
+ UTILITY: 'UTILITY',
+ ARTS: 'ARTS',
+ CRAFTS: 'CRAFTS',
+ FASHION: 'FASHION',
+ HOUSES: 'HOUSES',
+ GARDENS: 'GARDENS',
+ FLOWERS: 'FLOWERS',
+ HOLIDAYS: 'HOLIDAYS'
};
export namespace Export {
@@ -63,7 +73,15 @@ export namespace GooglePhotos {
mediaItems: MediaItem[];
}
- export const CollectionToAlbum = async (collection: Doc, title?: string, descriptionKey?: string): Promise<Opt<AlbumCreationResult>> => {
+ export interface AlbumCreationOptions {
+ collection: Doc;
+ title?: string;
+ descriptionKey?: string;
+ tag?: boolean;
+ }
+
+ export const CollectionToAlbum = async (options: AlbumCreationOptions): Promise<Opt<AlbumCreationResult>> => {
+ const { collection, title, descriptionKey, tag } = options;
const dataDocument = Doc.GetProto(collection);
const images = ((await DocListCastAsync(dataDocument.data)) || []).filter(doc => Cast(doc.data, ImageField));
if (!images || !images.length) {
@@ -71,9 +89,24 @@ export namespace GooglePhotos {
}
const resolved = title ? title : (StrCast(collection.title) || `Dash Collection (${collection[Id]}`);
const { id } = await Create.Album(resolved);
- const result = await Transactions.UploadImages(images, { id }, descriptionKey);
- if (result) {
- const mediaItems = result.newMediaItemResults.map(item => item.mediaItem);
+ const newMediaItemResults = await Transactions.UploadImages(images, { id }, descriptionKey);
+ if (newMediaItemResults) {
+ const mediaItems = newMediaItemResults.map(item => item.mediaItem);
+ if (mediaItems.length !== images.length) {
+ throw new AssertionError({ actual: mediaItems.length, expected: images.length });
+ }
+ const idMapping = new Doc;
+ for (let i = 0; i < images.length; i++) {
+ const image = images[i];
+ const mediaItem = mediaItems[i];
+ image.googlePhotosId = mediaItem.id;
+ image.googlePhotosUrl = mediaItem.baseUrl || mediaItem.productUrl;
+ idMapping[mediaItem.id] = image;
+ }
+ collection.googlePhotosIdMapping = idMapping;
+ if (tag) {
+ await Query.AppendImageMetadata(collection);
+ }
return { albumId: id, mediaItems };
}
};
@@ -101,21 +134,32 @@ export namespace GooglePhotos {
export namespace Query {
- export const AppendImageMetadata = (sources: (Doc | string)[]) => {
- let keys = Object.keys(ContentCategories);
- let included: string[] = [];
- let excluded: string[] = [];
- for (let i = 0; i < keys.length; i++) {
- for (let j = 0; j < keys.length; j++) {
- let value = ContentCategories[keys[i] as keyof typeof ContentCategories];
- if (j === i) {
- included.push(value);
- } else {
- excluded.push(value);
+ export const AppendImageMetadata = async (collection: Doc) => {
+ const idMapping = await Cast(collection.googlePhotosIdMapping, Doc);
+ if (!idMapping) {
+ throw new Error("Appending image metadata requires that the targeted collection have already been mapped to an album!");
+ }
+ const images = await DocListCastAsync(collection.data);
+ images && images.forEach(image => image.googlePhotosTags = new List());
+ const values = Object.values(ContentCategories);
+ for (let value of values) {
+ console.log("Searching for ", value);
+ const results = await Search({ included: [value] });
+ if (results.mediaItems) {
+ console.log(`${results.mediaItems.length} found!`);
+ const ids = results.mediaItems.map(item => item.id);
+ for (let id of ids) {
+ const image = await Cast(idMapping[id], Doc);
+ if (image) {
+ const tags = Cast(image.googlePhotosTags, listSpec("string"))!;
+ if (!tags.includes(value)) {
+ tags.push(value);
+ console.log(`${value}: ${id}`);
+ }
+ }
}
}
- //...
- included = excluded = [];
+ console.log();
}
};
@@ -125,20 +169,22 @@ export namespace GooglePhotos {
}
const DefaultSearchOptions: SearchOptions = {
- pageSize: 20,
+ pageSize: 50,
included: [],
excluded: [],
date: undefined,
includeArchivedMedia: true,
+ excludeNonAppCreatedData: false,
type: MediaType.ALL_MEDIA,
};
export interface SearchOptions {
pageSize: number;
- included: ContentCategories[];
- excluded: ContentCategories[];
+ included: string[];
+ excluded: string[];
date: Opt<Date | DateRange>;
includeArchivedMedia: boolean;
+ excludeNonAppCreatedData: boolean;
type: MediaType;
}
@@ -173,7 +219,7 @@ export namespace GooglePhotos {
filters.setMediaTypeFilter(new photos.MediaTypeFilter(options.type || MediaType.ALL_MEDIA));
return new Promise<SearchResponse>(resolve => {
- photos.mediaItems.search(filters, options.pageSize || 20).then(resolve);
+ photos.mediaItems.search(filters, options.pageSize || 100).then(resolve);
});
};
@@ -183,7 +229,7 @@ export namespace GooglePhotos {
}
- export namespace Create {
+ namespace Create {
export const Album = async (title: string) => {
return (await endpoint()).albums.create(title);
@@ -211,40 +257,34 @@ export namespace GooglePhotos {
return uploads;
};
- export const UploadThenFetch = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption") => {
- const result = await UploadImages(sources, album, descriptionKey);
- if (!result) {
+ export const UploadThenFetch = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => {
+ const newMediaItems = await UploadImages(sources, album, descriptionKey);
+ if (!newMediaItems) {
return undefined;
}
- const baseUrls: string[] = await Promise.all(result.newMediaItemResults.map((result: any) => {
- return new Promise<string>(resolve => Query.GetImage(result.mediaItem.id).then(item => resolve(item.baseUrl)));
+ const baseUrls: string[] = await Promise.all(newMediaItems.map(item => {
+ return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
}));
return baseUrls;
};
- export const UploadImages = async (sources: (Doc | string)[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<MediaItemCreationResult>> => {
+ export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<NewMediaItemResult[]>> => {
if (album && "title" in album) {
album = await Create.Album(album.title);
}
const media: MediaInput[] = [];
sources.forEach(source => {
- let url: string;
- let description: string;
- if (source instanceof Doc) {
- const data = Cast(Doc.GetProto(source).data, ImageField);
- if (!data) {
- return;
- }
- url = data.url.href;
- description = parseDescription(source, descriptionKey);
- } else {
- url = source;
- description = Utils.GenerateGuid();
+ const data = Cast(Doc.GetProto(source).data, ImageField);
+ if (!data) {
+ return;
}
+ const url = data.url.href;
+ const description = parseDescription(source, descriptionKey);
media.push({ url, description });
});
if (media.length) {
- return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
+ const uploads: NewMediaItemResult[] = await PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
+ return uploads;
}
};
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index d58c02ce5..348f216a5 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -74,13 +74,12 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
runInAction(() => this.uploading = true);
- let promises: Promise<void>[] = [];
let docs: Doc[] = [];
let files = e.target.files;
if (!files || files.length === 0) return;
- let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
+ let directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0];
let validated: File[] = [];
for (let i = 0; i < files.length; i++) {
@@ -117,8 +116,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
console.log(`(${this.quota - this.remaining}/${this.quota}) ${upload.name}`);
}));
- await GooglePhotos.Transactions.UploadImages(docs, { title: directory });
-
for (let i = 0; i < docs.length; i++) {
let doc = docs[i];
doc.size = sizes[i];
@@ -142,11 +139,11 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
let parent = this.props.ContainingCollectionView;
if (parent) {
let importContainer = Docs.Create.StackingDocument(docs, options);
+ await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
importContainer.singleColumn = false;
Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
DocumentManager.Instance.jumpToDocument(importContainer, true);
-
}
runInAction(() => {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8d10a91ce..28edf181b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -130,7 +130,7 @@ export class MainView extends React.Component {
window.removeEventListener("keydown", KeyManager.Instance.handle);
window.addEventListener("keydown", KeyManager.Instance.handle);
- // this.executeGooglePhotosRoutine();
+ this.executeGooglePhotosRoutine();
reaction(() => {
let workspaces = CurrentUserUtils.UserDocument.workspaces;
@@ -149,14 +149,15 @@ export class MainView extends React.Component {
}, { fireImmediately: true });
}
- // executeGooglePhotosRoutine = async () => {
- // let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- // let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" });
- // doc.caption = "Well isn't this a nice cat image!";
- // let photos = await GooglePhotos.endpoint();
- // let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id;
- // console.log(await GooglePhotos.UploadImages([doc], { id: albumId }));
- // }
+ executeGooglePhotosRoutine = async () => {
+ // let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
+ // let doc = Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" });
+ // doc.caption = "Well isn't this a nice cat image!";
+ // let photos = await GooglePhotos.endpoint();
+ // let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id;
+ // console.log(await GooglePhotos.UploadImages([doc], { id: albumId }));
+ GooglePhotos.Query.Search({ included: [GooglePhotos.ContentCategories.ANIMALS] }).then(console.log);
+ }
componentWillUnMount() {
window.removeEventListener("keydown", KeyManager.Instance.handle);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a51f783ad..a38f42751 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -594,7 +594,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
}
if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum(this.props.Document).then(console.log), icon: "caret-square-right" });
+ cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ cm.addItem({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.AppendImageMetadata(this.props.Document), icon: "caret-square-right" });
}
let existingMake = ContextMenu.Instance.findByDescription("Make...");
let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index 27737782b..6afe4ddfd 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -298,7 +298,7 @@ export namespace RichTextUtils {
const length = node.nodeSize;
const attrs = node.attrs;
const uri = attrs.src;
- const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([uri]);
+ const baseUrls = await GooglePhotos.Transactions.UploadThenFetch([Docs.Create.ImageDocument(uri)]);
if (!baseUrls) {
continue;
}
diff --git a/src/server/apis/google/CustomizedWrapper/filters.js b/src/server/apis/google/CustomizedWrapper/filters.js
new file mode 100644
index 000000000..576a90b75
--- /dev/null
+++ b/src/server/apis/google/CustomizedWrapper/filters.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const DateFilter = require('../common/date_filter');
+const MediaTypeFilter = require('./media_type_filter');
+const ContentFilter = require('./content_filter');
+
+class Filters {
+ constructor(includeArchivedMedia = false) {
+ this.includeArchivedMedia = includeArchivedMedia;
+ }
+
+ setDateFilter(dateFilter) {
+ this.dateFilter = dateFilter;
+ return this;
+ }
+
+ setContentFilter(contentFilter) {
+ this.contentFilter = contentFilter;
+ return this;
+ }
+
+ setMediaTypeFilter(mediaTypeFilter) {
+ this.mediaTypeFilter = mediaTypeFilter;
+ return this;
+ }
+
+ setIncludeArchivedMedia(includeArchivedMedia) {
+ this.includeArchivedMedia = includeArchivedMedia;
+ return this;
+ }
+
+ toJSON() {
+ return {
+ dateFilter: this.dateFilter instanceof DateFilter ? this.dateFilter.toJSON() : this.dateFilter,
+ mediaTypeFilter: this.mediaTypeFilter instanceof MediaTypeFilter ?
+ this.mediaTypeFilter.toJSON() :
+ this.mediaTypeFilter,
+ contentFilter: this.contentFilter instanceof ContentFilter ?
+ this.contentFilter.toJSON() :
+ this.contentFilter,
+ includeArchivedMedia: this.includeArchivedMedia
+ };
+ }
+}
+
+module.exports = Filters; \ No newline at end of file
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
index 447ed23ac..1a8adc836 100644
--- a/src/server/apis/google/GooglePhotosUploadUtils.ts
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -5,7 +5,7 @@ import { Utils } from '../../../Utils';
import * as path from 'path';
import { Opt } from '../../../new_fields/Doc';
import * as sharp from 'sharp';
-import { MediaItemCreationResult } from './SharedTypes';
+import { MediaItemCreationResult, NewMediaItemResult } from './SharedTypes';
const uploadDirectory = path.join(__dirname, "../../public/files/");
@@ -55,24 +55,34 @@ export namespace GooglePhotosUploadUtils {
- export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }): Promise<MediaItemCreationResult> => {
- return new Promise<MediaItemCreationResult>((resolve, reject) => {
+ export const CreateMediaItems = async (newMediaItems: any[], album?: { id: string }): Promise<MediaItemCreationResult> => {
+ const quota = newMediaItems.length;
+ let handled = 0;
+ const newMediaItemResults: NewMediaItemResult[] = [];
+ while (handled < quota) {
+ const cap = Math.min(newMediaItems.length, handled + 50);
+ const batch = newMediaItems.slice(handled, cap);
+ console.log(batch.length);
const parameters = {
method: 'POST',
headers: headers('json'),
uri: prepend('mediaItems:batchCreate'),
- body: { newMediaItems } as any,
+ body: { newMediaItems: batch } as any,
json: true
};
album && (parameters.body.albumId = album.id);
- request(parameters, (error, _response, body) => {
- if (error) {
- reject(error);
- } else {
- resolve(body);
- }
- });
- });
+ newMediaItemResults.push(...(await new Promise<MediaItemCreationResult>((resolve, reject) => {
+ request(parameters, (error, _response, body) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(body);
+ }
+ });
+ })).newMediaItemResults);
+ handled = cap;
+ }
+ return { newMediaItemResults };
};
}
diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json
index 22d57d744..0c06f68b7 100644
--- a/src/server/credentials/google_docs_token.json
+++ b/src/server/credentials/google_docs_token.json
@@ -1 +1 @@
-{"access_token":"ya29.GlyAB5T3dgJqWuYBcLaT94wQo7MZkmzJQZxDB2sSU95mdhW24E3diuFdLeNsUDVI57D3S765RweMnL98d-fdgu1dRxpzkV_J_3rLih99pZ8A4d6jVdm1354UT4py_w","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568161931458} \ No newline at end of file
+{"access_token":"ya29.GlyAB7VxfbK7fwV9-lqu9NZ1-p73aC8KaEXAYGHFOIIgAhx40CCUgS07vy485y7O0x9RwK-7FL6P547SscD5bVlTlJkclP-9uupKxDaeez7Tc7o2pJwt6bgJlbbw7w","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1568220636395} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 2c3e76c55..507463841 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -831,7 +831,7 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => {
return _error(res, tokenError);
}
GooglePhotosUploadUtils.CreateMediaItems(newMediaItems, req.body.album).then(
- mediaItems => _success(res, mediaItems),
+ result => _success(res, result.newMediaItemResults),
error => _error(res, mediaError, error)
);
});