diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/google_docs/GooglePhotosClientUtils.ts | 126 | ||||
-rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 7 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 19 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 3 | ||||
-rw-r--r-- | src/new_fields/RichTextUtils.ts | 2 | ||||
-rw-r--r-- | src/server/apis/google/CustomizedWrapper/filters.js | 46 | ||||
-rw-r--r-- | src/server/apis/google/GooglePhotosUploadUtils.ts | 34 | ||||
-rw-r--r-- | src/server/credentials/google_docs_token.json | 2 | ||||
-rw-r--r-- | src/server/index.ts | 2 |
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) ); }); |