From d271205a94bcb2ebc524b70f8a3ff98398e69252 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 12 Aug 2019 21:39:16 -0400 Subject: exporting text to google docs almost supported --- src/server/Message.ts | 1 + src/server/RouteStore.ts | 3 +- src/server/apis/google/GoogleApiServerUtils.ts | 109 +++++++++++++ src/server/apis/youtube/youtubeApiSample.d.ts | 2 + src/server/apis/youtube/youtubeApiSample.js | 179 +++++++++++++++++++++ .../credentials/google_docs_credentials.json | 1 + src/server/credentials/google_docs_token.json | 1 + src/server/index.ts | 33 +++- src/server/youtubeApi/youtubeApiSample.d.ts | 2 - src/server/youtubeApi/youtubeApiSample.js | 179 --------------------- 10 files changed, 321 insertions(+), 189 deletions(-) create mode 100644 src/server/apis/google/GoogleApiServerUtils.ts create mode 100644 src/server/apis/youtube/youtubeApiSample.d.ts create mode 100644 src/server/apis/youtube/youtubeApiSample.js create mode 100644 src/server/credentials/google_docs_credentials.json create mode 100644 src/server/credentials/google_docs_token.json delete mode 100644 src/server/youtubeApi/youtubeApiSample.d.ts delete mode 100644 src/server/youtubeApi/youtubeApiSample.js (limited to 'src/server') diff --git a/src/server/Message.ts b/src/server/Message.ts index aaee143e8..4ec390ade 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -1,4 +1,5 @@ import { Utils } from "../Utils"; +import { google, docs_v1 } from "googleapis"; export class Message { private _name: string; diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index e30015e39..5d977006a 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -30,6 +30,7 @@ export enum RouteStore { reset = "/reset/:token", // APIS - cognitiveServices = "/cognitiveservices" + cognitiveServices = "/cognitiveservices", + googleDocs = "/googleDocs/" } \ No newline at end of file diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts new file mode 100644 index 000000000..817b2b696 --- /dev/null +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -0,0 +1,109 @@ +import { google, docs_v1 } from "googleapis"; +import { createInterface } from "readline"; +import { readFile, writeFile } from "fs"; +import { OAuth2Client } from "google-auth-library"; + +/** + * Server side authentication for Google Api queries. + */ +export namespace GoogleApiServerUtils { + + // If modifying these scopes, delete token.json. + const prefix = 'https://www.googleapis.com/auth/'; + const SCOPES = [ + 'documents.readonly', + 'documents', + 'drive', + 'drive.file', + ]; + // The file token.json stores the user's access and refresh tokens, and is + // created automatically when the authorization flow completes for the first + // time. + export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); + + export namespace Docs { + + export interface CredentialPaths { + credentials: string; + token: string; + } + + export type Endpoint = docs_v1.Docs; + + export const GetEndpoint = async (paths: CredentialPaths) => { + return new Promise((resolve, reject) => { + readFile(paths.credentials, (err, credentials) => { + if (err) { + reject(err); + return console.log('Error loading client secret file:', err); + } + return authorize(parseBuffer(credentials), paths.token).then(auth => { + resolve(google.docs({ version: "v1", auth })); + }); + }); + }); + }; + + } + + /** + * 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 { + 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) => { + 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); + }); + }); + } + + /** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback for the authorized client. + */ + function getNewToken(oAuth2Client: OAuth2Client, token_path: string) { + return new Promise((resolve, reject) => { + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES.map(relative => prefix + relative), + }); + console.log('Authorize this app by visiting this url:', authUrl); + const rl = createInterface({ + input: process.stdin, + output: process.stdout, + }); + rl.question('Enter the code from that page here: ', (code) => { + rl.close(); + oAuth2Client.getToken(code, (err, token) => { + if (err || !token) { + reject(err); + return console.error('Error retrieving access token', err); + } + oAuth2Client.setCredentials(token); + // Store the token to disk for later program executions + writeFile(token_path, JSON.stringify(token), (err) => { + if (err) { + console.error(err); + reject(err); + } + console.log('Token stored to', token_path); + }); + resolve(oAuth2Client); + }); + }); + }); + } + +} \ No newline at end of file diff --git a/src/server/apis/youtube/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts new file mode 100644 index 000000000..427f54608 --- /dev/null +++ b/src/server/apis/youtube/youtubeApiSample.d.ts @@ -0,0 +1,2 @@ +declare const YoutubeApi: any; +export = YoutubeApi; \ No newline at end of file diff --git a/src/server/apis/youtube/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js new file mode 100644 index 000000000..50b3c7b38 --- /dev/null +++ b/src/server/apis/youtube/youtubeApiSample.js @@ -0,0 +1,179 @@ +const fs = require('fs'); +const readline = require('readline'); +const { google } = require('googleapis'); +const OAuth2 = google.auth.OAuth2; + + +// If modifying these scopes, delete your previously saved credentials +// at ~/.credentials/youtube-nodejs-quickstart.json +let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; +let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH || + process.env.USERPROFILE) + '/.credentials/'; +let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json'; + +module.exports.readApiKey = (callback) => { + fs.readFile('client_secret.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + callback(content); + }); +} + +module.exports.authorizedGetChannel = (apiKey) => { + //this didnt get called + // Authorize a client with the loaded credentials, then call the YouTube API. + authorize(JSON.parse(apiKey), getChannel); +} + +module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { + authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); +} + +module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { + authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); +} + + +/** + * Create an OAuth2 client with the given credentials, and then execute the + * given callback function. + * + * @param {Object} credentials The authorization client credentials. + * @param {function} callback The callback to call with the authorized client. + */ +function authorize(credentials, callback, args = {}) { + let clientSecret = credentials.installed.client_secret; + let clientId = credentials.installed.client_id; + let redirectUrl = credentials.installed.redirect_uris[0]; + let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl); + + // Check if we have previously stored a token. + fs.readFile(TOKEN_PATH, function (err, token) { + if (err) { + getNewToken(oauth2Client, callback); + } else { + oauth2Client.credentials = JSON.parse(token); + callback(oauth2Client, args); + } + }); +} + +/** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * + * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback to call with the authorized + * client. + */ +function getNewToken(oauth2Client, callback) { + var authUrl = oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES + }); + console.log('Authorize this app by visiting this url: ', authUrl); + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + rl.question('Enter the code from that page here: ', function (code) { + rl.close(); + oauth2Client.getToken(code, function (err, token) { + if (err) { + console.log('Error while trying to retrieve access token', err); + return; + } + oauth2Client.credentials = token; + storeToken(token); + callback(oauth2Client); + }); + }); +} + +/** + * Store token to disk be used in later program executions. + * + * @param {Object} token The token to store to disk. + */ +function storeToken(token) { + try { + fs.mkdirSync(TOKEN_DIR); + } catch (err) { + if (err.code != 'EEXIST') { + throw err; + } + } + fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { + if (err) throw err; + console.log('Token stored to ' + TOKEN_PATH); + }); + console.log('Token stored to ' + TOKEN_PATH); +} + +/** + * Lists the names and IDs of up to 10 files. + * + * @param {google.auth.OAuth2} auth An authorized OAuth2 client. + */ +function getChannel(auth) { + var service = google.youtube('v3'); + service.channels.list({ + auth: auth, + part: 'snippet,contentDetails,statistics', + forUsername: 'GoogleDevelopers' + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + var channels = response.data.items; + if (channels.length == 0) { + console.log('No channel found.'); + } else { + console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + + 'it has %s views.', + channels[0].id, + channels[0].snippet.title, + channels[0].statistics.viewCount); + } + }); +} + +function getVideos(auth, args) { + let service = google.youtube('v3'); + service.search.list({ + auth: auth, + part: 'id, snippet', + type: 'video', + q: args.userInput, + maxResults: 10 + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + let videos = response.data.items; + args.callBack(videos); + }); +} + +function getVideoDetails(auth, args) { + if (args.videoIds === undefined) { + return; + } + let service = google.youtube('v3'); + service.videos.list({ + auth: auth, + part: 'contentDetails, statistics', + id: args.videoIds + }, function (err, response) { + if (err) { + console.log('The API returned an error from details: ' + err); + return; + } + let videoDetails = response.data.items; + args.callBack(videoDetails); + }); +} \ No newline at end of file diff --git a/src/server/credentials/google_docs_credentials.json b/src/server/credentials/google_docs_credentials.json new file mode 100644 index 000000000..8d097d363 --- /dev/null +++ b/src/server/credentials/google_docs_credentials.json @@ -0,0 +1 @@ +{"installed":{"client_id":"343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com","project_id":"quickstart-1565056383187","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_secret":"w8KIFSc0MQpmUYHed4qEzn8b","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} \ No newline at end of file diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json new file mode 100644 index 000000000..07c02d56c --- /dev/null +++ b/src/server/credentials/google_docs_token.json @@ -0,0 +1 @@ +{"access_token":"ya29.GltjB4-x03xFpd2NY2555cxg1xlT_ajqRi78M9osOfdOF2jTIjlPkn_UZL8cUwVP0DPC8rH3vhhg8RpspFe8Vewx92shAO3RPos_uMH0CUqEiCiZlaaB5I3Jq3Mv","refresh_token":"1/teUKUqGKMLjVqs-eed0L8omI02pzSxMUYaxGc2QxBw0","scope":"https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents.readonly","token_type":"Bearer","expiry_date":1565654175862} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index eae018f13..9986e62d6 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -14,7 +14,6 @@ import * as mobileDetect from 'mobile-detect'; import * as passport from 'passport'; import * as path from 'path'; import * as request from 'request'; -import * as rp from 'request-promise'; import * as io from 'socket.io'; import { Socket } from 'socket.io'; import * as webpack from 'webpack'; @@ -36,19 +35,16 @@ const port = 1050; // default port to listen const serverPort = 4321; import expressFlash = require('express-flash'); import flash = require('connect-flash'); -import c = require("crypto"); import { Search } from './Search'; -import { debug } from 'util'; import _ = require('lodash'); import * as Archiver from 'archiver'; -import * as AdmZip from 'adm-zip'; -import * as YoutubeApi from './youtubeApi/youtubeApiSample.js'; +import AdmZip from 'adm-zip'; +import * as YoutubeApi from "./apis/youtube/youtubeApiSample"; import { Response } from 'express-serve-static-core'; +import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); -var SolrNode = require('solr-node'); -var shell = require('shelljs'); const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest)); let youtubeApiKey: string; @@ -790,6 +786,29 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any } } +const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json"); +const token = path.join(__dirname, "./credentials/google_docs_token.json"); + +app.post(RouteStore.googleDocs + ":action", (req, res) => { + let parameters = req.body; + console.log(parameters, req.params.action); + + GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { + let results: Promise | undefined; + switch (req.params.action) { + case "create": + results = endpoint.documents.create(parameters).then(generated => generated.data.documentId); + break; + case "retrieve": + results = endpoint.documents.get(parameters).then(response => response.data); + break; + default: + results = undefined; + } + results && results.then(final => res.send(final)); + }); +}); + const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { "number": "_n", "string": "_t", diff --git a/src/server/youtubeApi/youtubeApiSample.d.ts b/src/server/youtubeApi/youtubeApiSample.d.ts deleted file mode 100644 index 427f54608..000000000 --- a/src/server/youtubeApi/youtubeApiSample.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const YoutubeApi: any; -export = YoutubeApi; \ No newline at end of file diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js deleted file mode 100644 index 50b3c7b38..000000000 --- a/src/server/youtubeApi/youtubeApiSample.js +++ /dev/null @@ -1,179 +0,0 @@ -const fs = require('fs'); -const readline = require('readline'); -const { google } = require('googleapis'); -const OAuth2 = google.auth.OAuth2; - - -// If modifying these scopes, delete your previously saved credentials -// at ~/.credentials/youtube-nodejs-quickstart.json -let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; -let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH || - process.env.USERPROFILE) + '/.credentials/'; -let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json'; - -module.exports.readApiKey = (callback) => { - fs.readFile('client_secret.json', function processClientSecrets(err, content) { - if (err) { - console.log('Error loading client secret file: ' + err); - return; - } - callback(content); - }); -} - -module.exports.authorizedGetChannel = (apiKey) => { - //this didnt get called - // Authorize a client with the loaded credentials, then call the YouTube API. - authorize(JSON.parse(apiKey), getChannel); -} - -module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { - authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); -} - -module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { - authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); -} - - -/** - * Create an OAuth2 client with the given credentials, and then execute the - * given callback function. - * - * @param {Object} credentials The authorization client credentials. - * @param {function} callback The callback to call with the authorized client. - */ -function authorize(credentials, callback, args = {}) { - let clientSecret = credentials.installed.client_secret; - let clientId = credentials.installed.client_id; - let redirectUrl = credentials.installed.redirect_uris[0]; - let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl); - - // Check if we have previously stored a token. - fs.readFile(TOKEN_PATH, function (err, token) { - if (err) { - getNewToken(oauth2Client, callback); - } else { - oauth2Client.credentials = JSON.parse(token); - callback(oauth2Client, args); - } - }); -} - -/** - * Get and store new token after prompting for user authorization, and then - * execute the given callback with the authorized OAuth2 client. - * - * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for. - * @param {getEventsCallback} callback The callback to call with the authorized - * client. - */ -function getNewToken(oauth2Client, callback) { - var authUrl = oauth2Client.generateAuthUrl({ - access_type: 'offline', - scope: SCOPES - }); - console.log('Authorize this app by visiting this url: ', authUrl); - var rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - rl.question('Enter the code from that page here: ', function (code) { - rl.close(); - oauth2Client.getToken(code, function (err, token) { - if (err) { - console.log('Error while trying to retrieve access token', err); - return; - } - oauth2Client.credentials = token; - storeToken(token); - callback(oauth2Client); - }); - }); -} - -/** - * Store token to disk be used in later program executions. - * - * @param {Object} token The token to store to disk. - */ -function storeToken(token) { - try { - fs.mkdirSync(TOKEN_DIR); - } catch (err) { - if (err.code != 'EEXIST') { - throw err; - } - } - fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { - if (err) throw err; - console.log('Token stored to ' + TOKEN_PATH); - }); - console.log('Token stored to ' + TOKEN_PATH); -} - -/** - * Lists the names and IDs of up to 10 files. - * - * @param {google.auth.OAuth2} auth An authorized OAuth2 client. - */ -function getChannel(auth) { - var service = google.youtube('v3'); - service.channels.list({ - auth: auth, - part: 'snippet,contentDetails,statistics', - forUsername: 'GoogleDevelopers' - }, function (err, response) { - if (err) { - console.log('The API returned an error: ' + err); - return; - } - var channels = response.data.items; - if (channels.length == 0) { - console.log('No channel found.'); - } else { - console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + - 'it has %s views.', - channels[0].id, - channels[0].snippet.title, - channels[0].statistics.viewCount); - } - }); -} - -function getVideos(auth, args) { - let service = google.youtube('v3'); - service.search.list({ - auth: auth, - part: 'id, snippet', - type: 'video', - q: args.userInput, - maxResults: 10 - }, function (err, response) { - if (err) { - console.log('The API returned an error: ' + err); - return; - } - let videos = response.data.items; - args.callBack(videos); - }); -} - -function getVideoDetails(auth, args) { - if (args.videoIds === undefined) { - return; - } - let service = google.youtube('v3'); - service.videos.list({ - auth: auth, - part: 'contentDetails, statistics', - id: args.videoIds - }, function (err, response) { - if (err) { - console.log('The API returned an error from details: ' + err); - return; - } - let videoDetails = response.data.items; - args.callBack(videoDetails); - }); -} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 278cabf67a77edb3dd3bd9bb392550eeb08ab910 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 13 Aug 2019 02:06:32 -0400 Subject: more util functions and clean up --- .../apis/google_docs/GoogleApiClientUtils.ts | 71 +++++++++++++++++++--- src/client/views/MainView.tsx | 24 +++++--- src/server/index.ts | 1 - 3 files changed, 78 insertions(+), 18 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 42e621f6b..71e5e1073 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,6 +1,7 @@ import { docs_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; +import { Opt } from "../../../new_fields/Doc"; export namespace GoogleApiClientUtils { @@ -11,33 +12,76 @@ export namespace GoogleApiClientUtils { Retrieve = "retrieve" } - export namespace Helpers { + export namespace Utils { export const fromRgb = (red: number, green: number, blue: number) => { return { color: { rgbColor: { red, green, blue } } }; }; + export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { + let fragments: string[] = []; + if (document.body && document.body.content) { + for (let element of document.body.content) { + if (element.paragraph && element.paragraph.elements) { + for (let inner of element.paragraph.elements) { + if (inner && inner.textRun) { + let fragment = inner.textRun.content; + fragment && fragments.push(fragment); + } + } + } + } + } + let text = fragments.join(""); + return removeNewlines ? text.ReplaceAll("\n", "") : text; + }; + } export const ExampleDocumentSchema = { title: "This is a Google Doc Created From Dash Web", body: { content: [ + { + endIndex: 1, + sectionBreak: { + sectionStyle: { + columnSeparatorStyle: "NONE", + contentDirection: "LEFT_TO_RIGHT" + } + } + }, { paragraph: { elements: [ { textRun: { - content: "And this is its bold, blue text!!!", + content: "And this is its bold, blue text!!!\n", textStyle: { bold: true, - backgroundColor: Helpers.fromRgb(0, 0, 1) + backgroundColor: Utils.fromRgb(0, 0, 1) } } } ] } - } + }, + { + paragraph: { + elements: [ + { + textRun: { + content: "And this is its bold, blue text!!!\n", + textStyle: { + bold: true, + backgroundColor: Utils.fromRgb(0, 0, 1) + } + } + } + ] + } + }, + ] as docs_v1.Schema$StructuralElement[] } } as docs_v1.Schema$Document; @@ -66,20 +110,27 @@ export namespace GoogleApiClientUtils { } }; - let path = RouteStore.googleDocs + Actions.Retrieve; - export const Retrieve = async (documentId: string): Promise => { + export const Read = async (documentId: string, removeNewlines = false): Promise> => { + return Retrieve(documentId).then(schema => { + return schema ? Utils.extractText(schema, removeNewlines) : undefined; + }); + }; + + export const Retrieve = async (documentId: string): Promise> => { + let path = RouteStore.googleDocs + Actions.Retrieve; let parameters = { documentId }; - let documentContents: any; + let schema: Opt; try { - documentContents = await PostToServer(path, parameters); + schema = await PostToServer(path, parameters); } catch (e) { console.error(e); - documentContents = undefined; + schema = undefined; } finally { - return documentContents; + return schema; } }; + } } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0a987de4a..3a5285a66 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -87,6 +87,11 @@ export class MainView extends React.Component { if (!("presentationView" in doc)) { doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })]); } + if (!("googleDocId" in doc)) { + GoogleApiClientUtils.Docs.Create().then(id => { + id && (doc.googleDocId = id); + }); + } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -132,8 +137,6 @@ export class MainView extends React.Component { window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); - GoogleApiClientUtils.Docs.Create().then(id => console.log(id)); - reaction(() => { let workspaces = CurrentUserUtils.UserDocument.workspaces; let recent = CurrentUserUtils.UserDocument.recentlyClosed; @@ -151,6 +154,18 @@ export class MainView extends React.Component { }, { fireImmediately: true }); } + componentDidMount() { + reaction(() => this.mainContainer, () => { + let main = this.mainContainer, googleDocId; + if (main && (googleDocId = StrCast(main.googleDocId))) { + GoogleApiClientUtils.Docs.Read(googleDocId, true).then(text => { + text && Utils.CopyText(text); + console.log(text); + }); + } + }); + } + componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); window.removeEventListener("pointerdown", this.globalPointerDown); @@ -573,11 +588,6 @@ export class MainView extends React.Component { render() { return (
- { - if (e.which === 13) { - GoogleApiClientUtils.Docs.Retrieve(e.currentTarget.value.trim()).then((res: any) => console.log(res)); - } - }} /> {this.dictationOverlay} {this.mainContent} diff --git a/src/server/index.ts b/src/server/index.ts index 9986e62d6..4ccb61681 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -791,7 +791,6 @@ const token = path.join(__dirname, "./credentials/google_docs_token.json"); app.post(RouteStore.googleDocs + ":action", (req, res) => { let parameters = req.body; - console.log(parameters, req.params.action); GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { let results: Promise | undefined; -- cgit v1.2.3-70-g09d2 From 2b829d1028a61869858ecd48e2f1801819e17488 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 13 Aug 2019 22:15:47 -0400 Subject: robust google doc export api and cleanup --- .../apis/google_docs/GoogleApiClientUtils.ts | 185 ++++++++++++--------- src/client/views/MainView.tsx | 11 +- src/client/views/nodes/FormattedTextBox.tsx | 24 ++- src/new_fields/RichTextField.ts | 13 ++ src/server/index.ts | 25 ++- 5 files changed, 162 insertions(+), 96 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 5e974b2e7..dda36f05a 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,7 +1,8 @@ import { docs_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; -import { Opt } from "../../../new_fields/Doc"; +import { Opt, Doc } from "../../../new_fields/Doc"; +import { isArray } from "util"; export namespace GoogleApiClientUtils { @@ -9,15 +10,12 @@ export namespace GoogleApiClientUtils { export enum Actions { Create = "create", - Retrieve = "retrieve" + Retrieve = "retrieve", + Update = "update" } export namespace Utils { - export const fromRgb = (red: number, green: number, blue: number) => { - return { color: { rgbColor: { red, green, blue } } }; - }; - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { let fragments: string[] = []; if (document.body && document.body.content) { @@ -36,55 +34,36 @@ export namespace GoogleApiClientUtils { return removeNewlines ? text.ReplaceAll("\n", "") : text; }; - } - - export const ExampleDocumentSchema = { - title: "This is a Google Doc Created From Dash Web", - body: { - content: [ - { - endIndex: 1, - sectionBreak: { - sectionStyle: { - columnSeparatorStyle: "NONE", - contentDirection: "LEFT_TO_RIGHT" + export const EndOf = (schema: docs_v1.Schema$Document): Opt => { + if (schema.body && schema.body.content) { + let paragraphs = schema.body.content.filter(el => el.paragraph); + if (paragraphs.length) { + let target = paragraphs[paragraphs.length - 1]; + if (target.paragraph && target.paragraph.elements) { + length = target.paragraph.elements.length; + if (length) { + let final = target.paragraph.elements[length - 1]; + return final.endIndex ? final.endIndex - 1 : undefined; } } - }, - { - paragraph: { - elements: [ - { - textRun: { - content: "And this is its bold, blue text!!!\n", - textStyle: { - bold: true, - backgroundColor: Utils.fromRgb(0, 0, 1) - } - } - } - ] - } - }, - { - paragraph: { - elements: [ - { - textRun: { - content: "And this is its bold, blue text!!!\n", - textStyle: { - bold: true, - backgroundColor: Utils.fromRgb(0, 0, 1) - } - } - } - ] - } - }, + } + } + }; - ] as docs_v1.Schema$StructuralElement[] - } - } as docs_v1.Schema$Document; + } + + export interface ReadOptions { + documentId: string; + removeNewlines?: boolean; + } + + export interface WriteOptions { + documentId?: string; + title?: string; + content: string | string[]; + index?: number; + store?: { receiver: Doc, key: string }; + } /** * After following the authentication routine, which connects this API call to the current signed in account @@ -96,45 +75,95 @@ export namespace GoogleApiClientUtils { * actual document body and its styling! * @returns the documentId of the newly generated document, or undefined if the creation process fails. */ - export const Create = async (schema?: docs_v1.Schema$Document): Promise => { + const Create = async (title?: string): Promise => { let path = RouteStore.googleDocs + Actions.Create; - let parameters = { requestBody: schema || ExampleDocumentSchema }; - let generatedId: string | undefined; + let parameters = { + requestBody: { + title: title || `Dash Export (${new Date().toDateString()})` + } + }; try { - generatedId = await PostToServer(path, parameters); - } catch (e) { - console.error(e); - generatedId = undefined; - } finally { - return generatedId; + let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + return schema.documentId; + } catch { + return undefined; } }; - export interface ReadOptions { - documentId: string; - removeNewlines?: boolean; - } + const Retrieve = async (documentId: string): Promise => { + let path = RouteStore.googleDocs + Actions.Retrieve; + let parameters = { + documentId + }; + try { + let schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + return schema; + } catch { + return undefined; + } + }; + + const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise => { + let path = RouteStore.googleDocs + Actions.Update; + let parameters = { + documentId, + requestBody: { + requests + } + }; + try { + let replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); + console.log(replies); + return replies; + } catch { + return undefined; + } + }; - export const Read = async (options: ReadOptions): Promise> => { + export const Read = async (options: ReadOptions): Promise => { return Retrieve(options.documentId).then(schema => { return schema ? Utils.extractText(schema, options.removeNewlines) : undefined; }); }; - export const Retrieve = async (documentId: string): Promise> => { - let path = RouteStore.googleDocs + Actions.Retrieve; - let parameters = { documentId }; - let schema: Opt; - try { - schema = await PostToServer(path, parameters); - } catch (e) { - console.error(e); - schema = undefined; - } finally { - return schema; - } + export const ReadLines = async (options: ReadOptions) => { + return Retrieve(options.documentId).then(schema => { + if (!schema) { + return undefined; + } + let lines = Utils.extractText(schema).split("\n"); + return options.removeNewlines ? lines.filter(line => line.length) : lines; + }); }; + export const Write = async (options: WriteOptions): Promise => { + let target = options.documentId; + if (!target) { + if (!(target = await Create(options.title))) { + return undefined; + } + } + let index = options.index; + if (!index) { + let schema = await Retrieve(target); + if (!schema || !(index = Utils.EndOf(schema))) { + return undefined; + } + } + let text = options.content; + let request = { + insertText: { + text: isArray(text) ? text.join("\n") : text, + location: { index } + } + }; + return Update(target, [request]).then(res => { + if (res && options.store) { + options.store.receiver[options.store.key] = res.documentId; + } + return res; + }); + }; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 705eacc0c..7b15e9624 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -87,11 +87,6 @@ export class MainView extends React.Component { if (!("presentationView" in doc)) { doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })]); } - if (!("googleDocId" in doc)) { - GoogleApiClientUtils.Docs.Create().then(id => { - id && (doc.googleDocId = id); - }); - } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -158,9 +153,9 @@ export class MainView extends React.Component { reaction(() => this.mainContainer, () => { let main = this.mainContainer, documentId; if (main && (documentId = StrCast(main.googleDocId))) { - GoogleApiClientUtils.Docs.Read({ documentId }).then(text => { - text && Utils.CopyText(text); - console.log(text); + let options = { documentId, removeNewlines: true }; + GoogleApiClientUtils.Docs.ReadLines(options).then(lines => { + console.log(lines); }); } }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 44b5d2c21..8c2af7c9e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,5 +1,5 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons'; +import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; @@ -38,9 +38,10 @@ import { For } from 'babel-types'; import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; +import { GoogleApiClientUtils } from '../../apis/google_docs/GoogleApiClientUtils'; library.add(faEdit); -library.add(faSmile, faTextHeight); +library.add(faSmile, faTextHeight, faUpload); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -58,6 +59,8 @@ const richTextSchema = createSchema({ documentText: "string" }); +const googleDocKey = "googleDocId"; + type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); @@ -661,7 +664,24 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); + if (!(googleDocKey in Doc.GetProto(this.props.Document))) { + ContextMenu.Instance.addItem({ description: "Export to Google Doc...", event: this.exportToGoogleDoc, icon: "upload" }); + } } + + exportToGoogleDoc = () => { + let dataDoc = Doc.GetProto(this.props.Document); + let data = Cast(dataDoc.data, RichTextField); + let content: string | undefined; + if (data && (content = data.plainText())) { + GoogleApiClientUtils.Docs.Write({ + title: StrCast(dataDoc.title), + store: { receiver: dataDoc, key: googleDocKey }, + content + }); + } + } + render() { let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 89799b2af..dc66813e0 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -9,6 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; + private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g; constructor(data: string) { super(); @@ -22,4 +23,16 @@ export class RichTextField extends ObjectField { [ToScriptString]() { return `new RichTextField("${this.Data}")`; } + + plainText = () => { + let contents = ""; + let matches: RegExpExecArray | null; + let considering = this.Data; + while ((matches = this.Extractor.exec(considering)) !== null) { + contents += matches[1]; + considering = considering.substring(matches.index + matches[0].length); + this.Extractor.lastIndex = 0; + } + return contents.length ? contents : undefined; + } } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 4ccb61681..abaa29658 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -42,6 +42,7 @@ import AdmZip from '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'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -170,6 +171,13 @@ const read_text_file = (relativePath: string) => { }); }; +const write_text_file = (relativePath: string, contents: any) => { + let target = path.join(__dirname, relativePath); + return new Promise((resolve, reject) => { + fs.writeFile(target, contents, (err) => err ? reject(err) : resolve()); + }); +}; + app.get("/version", (req, res) => { exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => { if (err) { @@ -790,21 +798,22 @@ const credentials = path.join(__dirname, "./credentials/google_docs_credentials. const token = path.join(__dirname, "./credentials/google_docs_token.json"); app.post(RouteStore.googleDocs + ":action", (req, res) => { - let parameters = req.body; - GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let results: Promise | undefined; + let results: Promise | undefined; + let documents = endpoint.documents; + let parameters = req.body; switch (req.params.action) { case "create": - results = endpoint.documents.create(parameters).then(generated => generated.data.documentId); + results = documents.create(parameters); break; case "retrieve": - results = endpoint.documents.get(parameters).then(response => response.data); + results = documents.get(parameters); + break; + case "update": + results = documents.batchUpdate(parameters); break; - default: - results = undefined; } - results && results.then(final => res.send(final)); + !results ? res.send(undefined) : results.then(response => res.send(response.data)); }); }); -- cgit v1.2.3-70-g09d2 From 48fcec82fa384ec260a02965f9f78c2e41256dd9 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 14 Aug 2019 03:41:11 -0400 Subject: clean up and regex improvement --- .../apis/google_docs/GoogleApiClientUtils.ts | 111 ++++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 15 ++- src/new_fields/RichTextField.ts | 4 +- src/server/index.ts | 35 ++++--- 4 files changed, 99 insertions(+), 66 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index f4fb87e0b..c6c7d7bd4 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,7 +1,7 @@ import { docs_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; -import { Opt, Doc } from "../../../new_fields/Doc"; +import { Opt } from "../../../new_fields/Doc"; import { isArray } from "util"; export namespace GoogleApiClientUtils { @@ -14,9 +14,42 @@ export namespace GoogleApiClientUtils { Update = "update" } + export type DocumentId = string; + export type Reference = DocumentId | CreateOptions; + export type TextContent = string | string[]; + export type IdHandler = (id: DocumentId) => any; + + export type CreationResult = Opt; + export type RetrievalResult = Opt; + export type UpdateResult = Opt; + export type ReadLinesResult = Opt; + export type ReadResult = Opt; + + export interface CreateOptions { + handler: IdHandler; // callback to process the documentId of the newly created Google Doc + title?: string; // if excluded, will use a default title annotated with the current date + } + + export interface RetrieveOptions { + documentId: DocumentId; + } + + export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; + + export interface WriteOptions { + content: TextContent; + reference: Reference; + index?: number; // if excluded, will compute the last index of the document and append the content there + } + + export interface UpdateOptions { + documentId: DocumentId; + requests: docs_v1.Schema$Request[]; + } + export namespace Utils { - export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false) => { + export const extractText = (document: docs_v1.Schema$Document, removeNewlines = false): string => { const fragments: string[] = []; if (document.body && document.body.content) { for (const element of document.body.content) { @@ -34,7 +67,7 @@ export namespace GoogleApiClientUtils { return removeNewlines ? text.ReplaceAll("\n", "") : text; }; - export const EndOf = (schema: docs_v1.Schema$Document): Opt => { + export const endOf = (schema: docs_v1.Schema$Document): number | undefined => { if (schema.body && schema.body.content) { const paragraphs = schema.body.content.filter(el => el.paragraph); if (paragraphs.length) { @@ -50,27 +83,8 @@ export namespace GoogleApiClientUtils { } }; - } + export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference); - export type IdHandler = (id: DocumentId) => any; - export interface CreateOptions { - handler: IdHandler; - // if excluded, will use a default title annotated with the current date - title?: string; - } - - export interface ReadOptions { - documentId: string; - // if exluded, will preserve newlines - removeNewlines?: boolean; - } - - export type DocumentId = string; - export interface WriteOptions { - content: string | string[]; - reference: DocumentId | CreateOptions; - // if excluded, will compute the last index of the document and append the content there - index?: number; } /** @@ -82,7 +96,7 @@ export namespace GoogleApiClientUtils { * to store the new documentId returned from the creation process * @returns the documentId of the newly generated document, or undefined if the creation process fails. */ - const Create = async (options: CreateOptions): Promise => { + export const create = async (options: CreateOptions): Promise => { const path = RouteStore.googleDocs + Actions.Create; const parameters = { requestBody: { @@ -101,43 +115,40 @@ export namespace GoogleApiClientUtils { } }; - const Retrieve = async (documentId: string): Promise => { + export const retrieve = async (options: RetrieveOptions): Promise => { const path = RouteStore.googleDocs + Actions.Retrieve; - const parameters = { - documentId - }; try { - const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); + const schema: RetrievalResult = await PostToServer(path, options); return schema; } catch { return undefined; } }; - const Update = async (documentId: string, requests: docs_v1.Schema$Request[]): Promise => { + export const update = async (options: UpdateOptions): Promise => { const path = RouteStore.googleDocs + Actions.Update; const parameters = { - documentId, + documentId: options.documentId, requestBody: { - requests + requests: options.requests } }; try { - const replies: docs_v1.Schema$BatchUpdateDocumentResponse = await PostToServer(path, parameters); + const replies: UpdateResult = await PostToServer(path, parameters); return replies; } catch { return undefined; } }; - export const Read = async (options: ReadOptions): Promise => { - return Retrieve(options.documentId).then(schema => { + export const read = async (options: ReadOptions): Promise => { + return retrieve(options).then(schema => { return schema ? Utils.extractText(schema, options.removeNewlines) : undefined; }); }; - export const ReadLines = async (options: ReadOptions) => { - return Retrieve(options.documentId).then(schema => { + export const readLines = async (options: ReadOptions): Promise => { + return retrieve(options).then(schema => { if (!schema) { return undefined; } @@ -146,27 +157,29 @@ export namespace GoogleApiClientUtils { }); }; - export const Write = async (options: WriteOptions): Promise => { - let documentId: string | undefined; - const ref = options.reference; - if (!(documentId = typeof ref === "string" ? ref : await Create(ref))) { + export const write = async (options: WriteOptions): Promise => { + const documentId = await Utils.initialize(options.reference); + if (!documentId) { return undefined; } let index = options.index; if (!index) { - let schema = await Retrieve(documentId); - if (!schema || !(index = Utils.EndOf(schema))) { + let schema = await retrieve({ documentId }); + if (!schema || !(index = Utils.endOf(schema))) { return undefined; } } const text = options.content; - const request = { - insertText: { - text: isArray(text) ? text.join("\n") : text, - location: { index } - } + const updateOptions = { + documentId, + requests: [{ + insertText: { + text: isArray(text) ? text.join("\n") : text, + location: { index } + } + }] }; - return Update(documentId, [request]); + return update(updateOptions); }; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 50ec27259..46aed9b2d 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -288,6 +288,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + componentWillMount() { + this.pollExportedCounterpart(); + } + + pollExportedCounterpart = async () => { + let dataDoc = Doc.GetProto(this.props.Document); + let documentId = StrCast(dataDoc[googleDocKey]); + if (documentId) { + let contents = await GoogleApiClientUtils.Docs.read({ documentId }); + contents ? console.log(contents) : delete dataDoc[googleDocKey]; + } + } + componentDidMount() { const config = { schema, @@ -675,7 +688,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!data) { return; } - GoogleApiClientUtils.Docs.Write({ + GoogleApiClientUtils.Docs.write({ reference: { title: StrCast(dataDoc.title), handler: id => dataDoc[googleDocKey] = id diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index 3e8803a34..4f782816c 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -9,7 +9,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; - private Extractor = /,\"text\":\"([^\"\}]*)\"\}/g; + private Extractor = /,\"text\":\"([^\}]*)\"\}/g; constructor(data: string) { super(); @@ -33,6 +33,6 @@ export class RichTextField extends ObjectField { considering = considering.substring(matches.index + matches[0].length); this.Extractor.lastIndex = 0; } - return contents; + return contents.ReplaceAll("\\", ""); } } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index abaa29658..ef1829f30 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -43,6 +43,8 @@ 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'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -797,23 +799,28 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json"); const token = path.join(__dirname, "./credentials/google_docs_token.json"); +type ApiResponse = Promise; +type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse; +type Action = "create" | "retrieve" | "update"; + +const EndpointHandlerMap = new Map([ + ["create", (api, params) => api.create(params)], + ["retrieve", (api, params) => api.get(params)], + ["update", (api, params) => api.batchUpdate(params)], +]); + app.post(RouteStore.googleDocs + ":action", (req, res) => { GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let results: Promise | undefined; - let documents = endpoint.documents; - let parameters = req.body; - switch (req.params.action) { - case "create": - results = documents.create(parameters); - break; - case "retrieve": - results = documents.get(parameters); - break; - case "update": - results = documents.batchUpdate(parameters); - break; + let handler = EndpointHandlerMap.get(req.params.action); + if (handler) { + let execute = handler(endpoint.documents, req.body).then( + response => res.send(response.data), + rejection => res.send(rejection) + ); + execute.catch(exception => res.send(exception)); + return; } - !results ? res.send(undefined) : results.then(response => res.send(response.data)); + res.send(undefined); }); }); -- cgit v1.2.3-70-g09d2 From 0e4729a8d634c67a3575761784b840a28694ba7a Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 19 Aug 2019 15:55:36 -0400 Subject: fixed treeview dragging. got rid of extra stuff from presentationview --- src/client/documents/Documents.ts | 2 +- src/client/views/MainView.tsx | 62 +- .../views/collections/CollectionDockingView.tsx | 27 +- .../views/collections/CollectionSchemaCells.tsx | 2 + .../views/collections/CollectionSchemaView.tsx | 4 + src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 18 +- .../CollectionFreeFormRemoteCursors.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 + src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/KeyValuePair.tsx | 1 + src/client/views/nodes/LinkMenu.tsx | 3 - src/client/views/nodes/PresBox.tsx | 249 +----- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 1 + .../views/presentationview/PresentationElement.tsx | 3 +- .../views/presentationview/PresentationList.tsx | 5 +- .../views/presentationview/PresentationView.scss | 4 +- .../views/presentationview/PresentationView.tsx | 994 --------------------- src/client/views/search/SearchItem.tsx | 1 + src/new_fields/Doc.ts | 55 +- src/new_fields/util.ts | 3 +- .../authentication/models/current_user_utils.ts | 23 +- 24 files changed, 172 insertions(+), 1304 deletions(-) delete mode 100644 src/client/views/presentationview/PresentationView.tsx (limited to 'src/server') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c551fd2a3..47df17329 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -290,7 +290,7 @@ export namespace Docs { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); if (!("author" in protoProps)) { - protoProps.author = CurrentUserUtils.email; + protoProps.author = Doc.CurrentUserEmail; } if (!("creationDate" in protoProps)) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7119a4fc3..7b7a5542d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -12,7 +12,6 @@ import { List } from '../../new_fields/List'; import { Id } from '../../new_fields/FieldSymbols'; import { InkTool } from '../../new_fields/InkField'; import { listSpec } from '../../new_fields/Schema'; -import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField'; import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; @@ -24,7 +23,7 @@ import { DictationManager } from '../util/DictationManager'; import { SetupDrag } from '../util/DragManager'; import { HistoryUtil } from '../util/History'; import { Transform } from '../util/Transform'; -import { UndoManager } from '../util/UndoManager'; +import { UndoManager, undoBatch } from '../util/UndoManager'; import { CollectionBaseView } from './collections/CollectionBaseView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionTreeView } from './collections/CollectionTreeView'; @@ -37,15 +36,16 @@ import { MainOverlayTextBox } from './MainOverlayTextBox'; import { DocumentView } from './nodes/DocumentView'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; -import { PresentationView } from './presentationview/PresentationView'; import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; +import { DocumentManager } from '../util/DocumentManager'; +import PresModeMenu from './presentationview/PresentationModeMenu'; +import { PresBox } from './nodes/PresBox'; @observer export class MainView extends React.Component { public static Instance: MainView; @observable addMenuToggle = React.createRef(); - @observable private _workspacesShown: boolean = false; @observable public pwidth: number = 0; @observable public pheight: number = 0; @@ -82,10 +82,6 @@ export class MainView extends React.Component { public isPointerDown = false; private set mainContainer(doc: Opt) { if (doc) { - if (!("presentationView" in doc)) { - let initialDoc = Docs.Create.TreeDocument([], { title: "Presentation" }); - doc.presentationView = Docs.Create.PresDocument(new List([initialDoc])); - } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -322,6 +318,7 @@ export class MainView extends React.Component { DataDoc={undefined} addDocument={undefined} addDocTab={emptyFunction} + pinToPres={emptyFunction} onClick={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} @@ -339,7 +336,6 @@ export class MainView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />} - {/* {presentationDoc ? : null} */}
} ; @@ -386,6 +382,7 @@ export class MainView extends React.Component { DataDoc={undefined} addDocument={undefined} addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} removeDocument={undefined} onClick={undefined} ScreenToLocalTransform={Transform.Identity} @@ -443,26 +440,24 @@ export class MainView extends React.Component { let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - // let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); - let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], [], { width: 200, height: 200, title: "a schema collection" })); - //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" })); - // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" })); let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })); let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })); let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" })); let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })); let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); - let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; - let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); + // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; + // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], + [React.createRef(), "table", "Add Presentation Trail", addPresNode], [React.createRef(), "globe-asia", "Add Website", addWebNode], [React.createRef(), "bolt", "Add Button", addButtonDocument], // [React.createRef(), "clone", "Add Docking Frame", addDockingNode], [React.createRef(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode - [React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], + //[React.createRef(), "play", "Add Youtube Searcher", addYoutubeSearcher], [React.createRef(), "file", "Add Document Dragger", addDragboxNode] ]; if (!ClientUtils.RELEASE) btns.unshift([React.createRef(), "cat", "Add Cat Image", addImageNode]); @@ -489,7 +484,7 @@ export class MainView extends React.Component {
  • -
  • +
  • {btns.map(btn => @@ -543,30 +538,20 @@ export class MainView extends React.Component { @observable isSearchVisible = false; @action.bound toggleSearch = () => { - // console.log("search toggling") this.isSearchVisible = !this.isSearchVisible; } togglePresentationView = () => { - let presDoc = this.presentationDoc; - if (!presDoc) { - return; - } - let isOpen = CollectionDockingView.Instance.Has(presDoc); - if (isOpen) { - return; - // CollectionDockingView.Instance.CloseRightSplit(presDoc); - //why?? It's throwing an error that seems impossible to fix. - //ToDo: - } else { - CollectionDockingView.Instance.AddRightSplit(presDoc, undefined); + if (CurrentUserUtils.UserDocument.curPresentation) { + let isOpen = DocumentManager.Instance.getDocumentView(CurrentUserUtils.UserDocument.curPresentation as Doc); + if (isOpen) { + CollectionDockingView.Instance.CloseRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc); + } else { + CollectionDockingView.Instance.AddRightSplit(CurrentUserUtils.UserDocument.curPresentation as Doc, undefined); + } } } - private get presentationDoc() { - let mainCont = this.mainContainer; - return mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; - } private get dictationOverlay() { let display = this.dictationOverlayVisible; let success = this.dictationSuccess; @@ -592,12 +577,21 @@ export class MainView extends React.Component { ); } + @computed get miniPresentation() { + let next = () => PresBox.CurrentPresentation.next(); + let back = () => PresBox.CurrentPresentation.back(); + let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres(); + let closePresMode = action(() => {PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document)}); + return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : + } + render() { return (
    {this.dictationOverlay} {this.mainContent} + {this.miniPresentation} {this.nodesMenu()} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3fcc61b76..47dfeb169 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -29,6 +29,8 @@ import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { Docs } from '../../documents/Documents'; import { DateField } from '../../../new_fields/DateField'; +import { List } from '../../../new_fields/List'; +import { DocumentType } from '../../documents/DocumentTypes'; library.add(faFile); @observer @@ -543,11 +545,31 @@ export class DockedFrameRenderer extends React.Component { })); } + /** + * Adds a document to the presentation view + **/ + @undoBatch + @action + public PinDoc(doc: Doc) { + if (doc.type === DocumentType.PRES) { + MainView.Instance.toggleMiniPresentation() + } + //add this new doc to props.Document + let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc; + if (curPres) { + const data = Cast(curPres.data, listSpec(Doc)); + if (data) { + data.push(doc); + } else { + curPres.data = new List([doc]); + } + } + } + componentDidMount() { this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); - // setTimeout(() => MainView.Instance.openPresentationView(), 2000); } componentWillUnmount() { @@ -592,6 +614,8 @@ export class DockedFrameRenderer extends React.Component { MainView.Instance.openWorkspace(doc); } else if (location === "onRight") { CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + } else if (location === "close") { + CollectionDockingView.Instance.CloseRightSplit(doc); } else { CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } @@ -618,6 +642,7 @@ export class DockedFrameRenderer extends React.Component { focus={emptyFunction} backgroundColor={returnEmptyString} addDocTab={this.addDocTab} + pinToPres={this.PinDoc} ContainingCollectionView={undefined} zoomToScale={emptyFunction} getScale={returnOne} />; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 7e3061354..551b485e7 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -40,6 +40,7 @@ export interface CellProps { fieldKey: string; renderDepth: number; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; @@ -160,6 +161,7 @@ export class CollectionSchemaCell extends React.Component { PanelHeight: returnZero, PanelWidth: returnZero, addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, ContentScaling: returnOne }; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 4537dcc85..221908dd2 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -251,6 +251,7 @@ export interface SchemaTableProps { active: () => boolean; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; isSelected: () => boolean; isFocused: (document: Doc) => boolean; setFocused: (document: Doc) => void; @@ -377,6 +378,7 @@ export class SchemaTable extends React.Component { fieldKey: this.props.fieldKey, renderDepth: this.props.renderDepth, addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, moveDocument: this.props.moveDocument, setIsEditing: this.setCellIsEditing, isEditable: isEditable, @@ -907,6 +909,7 @@ interface CollectionSchemaPreviewProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; setPreviewScript: (script: string) => void; previewScript?: string; } @@ -997,6 +1000,7 @@ export class CollectionSchemaPreview extends React.Component(schemaCtor: (doc: Doc) => T) { let ind; let doc = this.props.Document; let id = CurrentUserUtils.id; - let email = CurrentUserUtils.email; + let email = Doc.CurrentUserEmail; let pos = { x: position[0], y: position[1] }; if (id && email) { const proto = Doc.GetProto(doc); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 7424cc186..6b9cd57b3 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -39,6 +39,7 @@ export interface TreeViewProps { moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean; @@ -259,10 +260,10 @@ class TreeView extends React.Component { if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { let remDoc = (doc: Doc) => this.remove(doc, key); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); } else { contentElement = { const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { let remDoc = (doc: Doc) => this.remove(doc, expandKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); let docs = expandKey === "links" ? this.childLinks : this.childDocs; return
      {!docs ? (null) : TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
    ; } else if (this.treeViewExpandedView === "fields") { @@ -319,6 +320,7 @@ class TreeView extends React.Component { active={this.props.active} whenActiveChanged={emptyFunction as any} addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} setPreviewScript={emptyFunction}>
    ; @@ -395,6 +397,7 @@ class TreeView extends React.Component { move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void, + pinToPres: (document: Doc) => void, screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, active: () => boolean, @@ -471,6 +474,7 @@ class TreeView extends React.Component { moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} + pinToPres={pinToPres} ScreenToLocalTransform={screenToLocalXf} outerXf={outerXf} parentKey={key} @@ -554,7 +558,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { render() { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; - let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending)); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
    ([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; - Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true); + Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, !BoolCast(this.props.Document.stackingHeadersSortDescending)); }} /> {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)} {this.props.Document.allowClear ? this.renderClearButton : (null)}
      { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) + moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) }
    diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 3193f5624..b8148852d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -55,7 +55,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; @@ -614,7 +614,7 @@ export class DocumentView extends DocComponent(Docu let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" }); !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); - cm.addItem({ description: "Pin to Presentation", event: () => PresBox.Instance.PinDoc(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! + cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Download document", icon: "download", event: () => { @@ -661,7 +661,7 @@ export class DocumentView extends DocComponent(Docu try { let stuff = await rp.get(Utils.prepend(RouteStore.getUsers)); const users: User[] = JSON.parse(stuff); - usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({ + usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ description: email, event: async () => { const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); if (!userDocument) { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index cae975f30..f0f1b3b73 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -39,6 +39,7 @@ export interface FieldViewProps { selectOnLoad: boolean; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 534a42efc..8001b24a7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -68,6 +68,7 @@ export class KeyValuePair extends React.Component { PanelWidth: returnZero, PanelHeight: returnZero, addDocTab: returnZero, + pinToPres: returnZero, ContentScaling: returnOne }; let contents = ; diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 1a4af04f8..1908889e9 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -12,9 +12,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faTrash); -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { DocumentType } from "../../documents/Documents"; interface Props { docView: DocumentView; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 91c141540..cc042e008 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,22 +1,22 @@ import React = require("react"); -import { FieldViewProps, FieldView } from './FieldView'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { observable, action, runInAction, reaction, autorun, computed } from "mobx"; -import "../presentationview/PresentationView.scss"; -import { DocumentManager } from "../../util/DocumentManager"; -import { Utils } from "../../../Utils"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from "../../../Utils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { undoBatch } from "../../util/UndoManager"; import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit } from '@fortawesome/free-solid-svg-icons'; -import { Docs } from "../../documents/Documents"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "../presentationview/PresentationList"; +import "../presentationview/PresentationView.scss"; +import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenu } from "../ContextMenu"; library.add(faArrowLeft); library.add(faArrowRight); @@ -37,17 +37,14 @@ const expandedWidth = 450; @observer export class PresBox extends React.Component { //FieldViewProps? - @computed - private get presentationDocs() { - let source = Doc.GetProto(this.props.Document); - return DocListCast(source.data); - } public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } - //public static Instance: PresentationView; public static Instance: PresBox; + //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed. Let users create them, store them, as they see fit. + @computed get curPresentation() { return this.props.Document; } + //Mapping from presentation ids to a list of doc that represent a group @observable groupMappings: Map = new Map(); //mapping from docs to their rendered component @@ -59,9 +56,6 @@ export class PresBox extends React.Component { //FieldViewProps? //back-up so that presentation stays the way it's when refreshed @observable presGroupBackUp: Doc = new Doc(); @observable presButtonBackUp: Doc = new Doc(); - - //Keeping track of the doc for the current presentation - @observable curPresentation: Doc = new Doc(); //Mapping of guids to presentations. @observable presentationsMapping: Map = new Map(); //Mapping of presentations to guid, so that select option values can be given. @@ -77,12 +71,14 @@ export class PresBox extends React.Component { //FieldViewProps? @observable opacity = 1; @observable persistOpacity = true; @observable labelOpacity = 0; + @observable presMode = false; + + @observable public static CurrentPresentation: PresBox; //initilize class variables - constructor(props: FieldViewProps) { //FieldViewProps? + constructor(props: FieldViewProps) { super(props); - //PresentationView.Instance = this; - PresBox.Instance = this; + runInAction(() => PresBox.CurrentPresentation = this); } @action @@ -94,32 +90,10 @@ export class PresBox extends React.Component { //FieldViewProps? } } - //The first lifecycle function that gets called to set up the current presentation. - async componentWillMount() { - this.presentationDocs.forEach(async (doc, index: number) => { - - //For each presentation received from mainContainer, a mapping is created. - let curDoc: Doc = await doc; - let newGuid = Utils.GenerateGuid(); - this.presentationsKeyMapping.set(curDoc, newGuid); - this.presentationsMapping.set(newGuid, curDoc); - - //The Presentation at first index gets set as default start presentation - if (index === 0) { - runInAction(() => this.currentSelectedPresValue = newGuid); - runInAction(() => this.curPresentation = curDoc); - } - }); - } - - //Second lifecycle function that gets called when component mounts. It makes sure to + //Second lifecycle function that gets called when component mounts. It makes sure toS //get the back-up information from previous session for the current presentation. async componentDidMount() { - let docAtZero = await this.presentationDocs[0]; - runInAction(() => this.curPresentation = docAtZero); - this.setPresentationBackUps(); - } @@ -212,7 +186,6 @@ export class PresBox extends React.Component { //FieldViewProps? //observable means render is re-called every time variable is changed @observable collapsed: boolean = false; - closePresentation = action(() => this.curPresentation.width = 0); next = async () => { const current = NumCast(this.curPresentation.selectedDoc); //asking to get document at current index @@ -556,23 +529,6 @@ export class PresBox extends React.Component { //FieldViewProps? runInAction(() => this.groupMappings = new Map()); } - /** - * Adds a document to the presentation view - **/ - @undoBatch - @action - public PinDoc(doc: Doc) { - //add this new doc to props.Document - const data = Cast(this.curPresentation.data, listSpec(Doc)); - if (data) { - data.push(doc); - } else { - this.curPresentation.data = new List([doc]); - } - - this.toggle(true); - } - //Function that sets the store of the children docs. @action setChildrenDocs = (docList: Doc[]) => { @@ -648,72 +604,15 @@ export class PresBox extends React.Component { //FieldViewProps? } - /** - * The function that is called to add a new presentation to the presentationView. - * It sets up te mappings and local copies of it. Resets the groupings and presentation. - * Makes the new presentation current selected, and retrieve the back-Ups if present. - */ - @action - addNewPresentation = (presTitle: string) => { - //creating a new presentation doc - let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle }); - let presDocs = Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc)); - presDocs && presDocs.push(newPresentationDoc); - - //setting that new doc as current - this.curPresentation = newPresentationDoc; - - //storing the doc in local copies for easier access - let newGuid = Utils.GenerateGuid(); - this.presentationsMapping.set(newGuid, newPresentationDoc); - this.presentationsKeyMapping.set(newPresentationDoc, newGuid); - - //resetting the previous presentation's actions so that new presentation can be loaded. - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = newGuid; - this.setPresentationBackUps(); - - } - - /** - * The function that is called to change the current selected presentation. - * Changes the presentation, also resetting groupings and presentation in process. - * Plus retrieving the backUps for the newly selected presentation. - */ - @action - getSelectedPresentation = (e: React.ChangeEvent) => { - //get the guid of the selected presentation - let selectedGuid = e.target.value; - //set that as current presentation - this.curPresentation = this.presentationsMapping.get(selectedGuid)!; - - //reset current Presentations local things so that new one can be loaded - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = selectedGuid; - this.setPresentationBackUps(); - - - } /** * The function that is called to render either select for presentations, or title inputting. */ renderSelectOrPresSelection = () => { - let presentationList = this.presentationDocs; if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { return this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; } else { - return ; + return (null); } } @@ -726,87 +625,12 @@ export class PresBox extends React.Component { //FieldViewProps? if (e.keyCode === 13) { let presTitle = this.titleInputElement!.value; this.titleInputElement!.value = ""; - if (this.PresTitleInputOpen) { - if (presTitle === "") { - presTitle = "Presentation"; - } - this.PresTitleInputOpen = false; - this.addNewPresentation(presTitle); - } else if (this.PresTitleChangeOpen) { + if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; this.changePresentationTitle(presTitle); } } } - - /** - * The function that is called to remove a presentation from all its copies, and the main Container's - * list. Sets up the next presentation as current. - */ - @action - removePresentation = async () => { - if (this.presentationsMapping.size !== 1) { - let presentationList = this.presentationDocs; - let batch = UndoManager.StartBatch("presRemoval"); - - //getting the presentation that will be removed - let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!); - //that presentation is removed - presentationList!.splice(presentationList.indexOf(removedDoc!), 1); - - //its mappings are removed from local copies - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(this.currentSelectedPresValue!); - - //the next presentation is set as current - let remainingPresentations = this.presentationsMapping.values(); - let nextDoc = remainingPresentations.next().value; - this.curPresentation = nextDoc; - - - //Storing these for being able to undo changes - let curGuid = this.currentSelectedPresValue!; - let curPresStatus = this.presStatus; - - //resetting the groups and presentation actions so that next presentation gets loaded - this.resetGroupIds(); - this.resetPresentation(); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - //Storing for undo - let currentGroups = this.groupMappings; - let curPresElemMapping = this.presElementsMappings; - - //Event to undo actions that are not related to doc directly, aka. local things - UndoManager.AddEvent({ - undo: action(() => { - this.curPresentation = removedDoc!; - this.presentationsMapping.set(curGuid, removedDoc!); - this.presentationsKeyMapping.set(removedDoc!, curGuid); - this.currentSelectedPresValue = curGuid; - - this.presStatus = curPresStatus; - this.groupMappings = currentGroups; - this.presElementsMappings = curPresElemMapping; - this.setPresentationBackUps(); - - }), - redo: action(() => { - this.curPresentation = nextDoc; - this.presStatus = false; - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(curGuid); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - }), - }); - - batch.end(); - } - } - /** * The function that is called to change title of presentation to what user entered. */ @@ -822,26 +646,23 @@ export class PresBox extends React.Component { //FieldViewProps? this.presElementsMappings.set(keyDoc, elem); } + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" }); + ContextMenu.Instance.addItem({ + description: "Toggle Minimized Mode", event: action(() => { + this.presMode = !this.presMode; + if (this.presMode) this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + }), icon: "asterisk" + }); + } render() { let width = "100%"; //NumCast(this.curPresentation.width) - return ( -
    !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease", pointerEvents: "all" }}> -
    - {this.renderSelectOrPresSelection()} - {/**this.closePresentation CLICK does not work?! Also without the*/} - - - -
    +
    !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu} + onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} + style={{ width: width, opacity: this.opacity, }}>
    {this.renderPlayPauseButton()} diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 7ba7b6d14..6f77a0a5b 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -6,10 +6,10 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { DocumentManager } from "../../util/DocumentManager"; -import { PresentationView } from "../presentationview/PresentationView"; import PDFMenu from "./PDFMenu"; import "./Annotation.scss"; import { scale } from "./PDFViewer"; +import { PresBox } from "../nodes/PresBox"; interface IAnnotationProps { anno: Doc; @@ -18,6 +18,7 @@ interface IAnnotationProps { fieldExtensionDoc: Doc; scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; } export default class Annotation extends React.Component { @@ -37,6 +38,7 @@ interface IRegionAnnotationProps { fieldExtensionDoc: Doc; scrollTo?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; document: Doc; } @@ -81,7 +83,7 @@ class RegionAnnotation extends React.Component { pinToPres = () => { let group = FieldValue(Cast(this.props.document.group, Doc)); - group && PresentationView.Instance.PinDoc(group); + group && this.props.pinToPres(group); } @action diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 08674720d..258e218f0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -36,6 +36,7 @@ interface IViewerProps { active: () => boolean; setPanY?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; } diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index d98b66324..912970a50 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -15,7 +15,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { ContextMenu } from "../ContextMenu"; import { Transform } from "../../util/Transform"; import { DocumentView } from "../nodes/DocumentView"; -import { DocumentType } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import React = require("react"); @@ -839,6 +839,7 @@ export default class PresentationElement extends React.Component 350} PanelHeight={() => 90} diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index e853c4070..288ade042 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -6,10 +6,7 @@ import { Utils } from "../../../Utils"; import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; -import PresentationElement, { buttonIndex } from "./PresentationElement"; -import "../../../new_fields/Doc"; - - +import PresentationElement from "./PresentationElement"; interface PresListProps { diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 65b09c833..4f5858f20 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -7,7 +7,9 @@ top: 0; bottom: 0; letter-spacing: 2px; - + overflow: hidden; + transition: 0.7s opacity ease; + pointer-events: all; } .presentationView-item { diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx deleted file mode 100644 index bea70f00b..000000000 --- a/src/client/views/presentationview/PresentationView.tsx +++ /dev/null @@ -1,994 +0,0 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import { observable, action, runInAction, reaction, autorun } from "mobx"; -import "./PresentationView.scss"; -import { DocumentManager } from "../../util/DocumentManager"; -import { Utils } from "../../../Utils"; -import { Doc, DocListCast, DocListCastAsync, WidthSym } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { List } from "../../../new_fields/List"; -import PresentationElement, { buttonIndex } from "./PresentationElement"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit, faEye } from '@fortawesome/free-solid-svg-icons'; -import { Docs } from "../../documents/Documents"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import PresentationViewList from "./PresentationList"; -import PresModeMenu from "./PresentationModeMenu"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; - -library.add(faArrowLeft); -library.add(faArrowRight); -library.add(faPlay); -library.add(faStop); -library.add(faPlus); -library.add(faTimes); -library.add(faMinus); -library.add(faEdit); -library.add(faEye); - - -export interface PresViewProps { - Documents: List; -} - -const expandedWidth = 400; -const presMinWidth = 300; - -@observer -export class PresentationView extends React.Component { - public static Instance: PresentationView; - - //Mapping from presentation ids to a list of doc that represent a group - @observable groupMappings: Map = new Map(); - //mapping from docs to their rendered component - @observable presElementsMappings: Map = new Map(); - //variable that holds all the docs in the presentation - @observable childrenDocs: Doc[] = []; - //variable to hold if presentation is started - @observable presStatus: boolean = false; - //back-up so that presentation stays the way it's when refreshed - @observable presGroupBackUp: Doc = new Doc(); - @observable presButtonBackUp: Doc = new Doc(); - - //Keeping track of the doc for the current presentation - @observable curPresentation: Doc = new Doc(); - //Mapping of guids to presentations. - @observable presentationsMapping: Map = new Map(); - //Mapping of presentations to guid, so that select option values can be given. - @observable presentationsKeyMapping: Map = new Map(); - //Variable to keep track of guid of the current presentation - @observable currentSelectedPresValue: string | undefined; - //A flag to keep track if title input is open, which is used in rendering. - @observable PresTitleInputOpen: boolean = false; - //Variable that holds reference to title input, so that new presentations get titles assigned. - @observable titleInputElement: HTMLInputElement | undefined; - @observable PresTitleChangeOpen: boolean = false; - @observable presMode: boolean = false; - - - @observable opacity = 1; - @observable persistOpacity = true; - @observable labelOpacity = 0; - - //initilize class variables - constructor(props: PresViewProps) { - super(props); - PresentationView.Instance = this; - } - - @action - toggle = (forcedValue: boolean | undefined) => { - if (forcedValue !== undefined) { - this.curPresentation.width = forcedValue ? expandedWidth : 0; - } else { - this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth; - } - } - - //The first lifecycle function that gets called to set up the current presentation. - async componentWillMount() { - - this.props.Documents.forEach(async (doc, index: number) => { - - //For each presentation received from mainContainer, a mapping is created. - let curDoc: Doc = await doc; - let newGuid = Utils.GenerateGuid(); - this.presentationsKeyMapping.set(curDoc, newGuid); - this.presentationsMapping.set(newGuid, curDoc); - - //The Presentation at first index gets set as default start presentation - if (index === 0) { - runInAction(() => this.currentSelectedPresValue = newGuid); - runInAction(() => this.curPresentation = curDoc); - } - }); - } - - //Second lifecycle function that gets called when component mounts. It makes sure to - //get the back-up information from previous session for the current presentation. - async componentDidMount() { - let docAtZero = await this.props.Documents[0]; - runInAction(() => this.curPresentation = docAtZero); - - this.setPresentationBackUps(); - - } - - - /** - * The function that retrieves the backUps for the current Presentation if present, - * otherwise initializes. - */ - setPresentationBackUps = async () => { - //getting both backUp documents - - let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc); - let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc); - //if instantiated before - if (castedGroupBackUp instanceof Promise) { - castedGroupBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presGroupBackUp = toAssign; - runInAction(() => this.presGroupBackUp = toAssign); - if (doc) { - if (toAssign[Id] === doc[Id]) { - this.retrieveGroupMappings(); - } - } - }); - - //if never instantiated a store doc yet - } else if (castedGroupBackUp instanceof Doc) { - let castedDoc: Doc = await castedGroupBackUp; - runInAction(() => this.presGroupBackUp = castedDoc); - this.retrieveGroupMappings(); - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presGroupBackUp = toAssign; - this.curPresentation.presGroupBackUp = toAssign; - - }); - - } - //if instantiated before - if (castedButtonBackUp instanceof Promise) { - castedButtonBackUp.then(doc => { - let toAssign = doc ? doc : new Doc(); - this.curPresentation.presButtonBackUp = toAssign; - runInAction(() => this.presButtonBackUp = toAssign); - }); - - //if never instantiated a store doc yet - } else if (castedButtonBackUp instanceof Doc) { - let castedDoc: Doc = await castedButtonBackUp; - runInAction(() => this.presButtonBackUp = castedDoc); - - } else { - runInAction(() => { - let toAssign = new Doc(); - this.presButtonBackUp = toAssign; - this.curPresentation.presButtonBackUp = toAssign; - }); - - } - - - //storing the presentation status,ie. whether it was stopped or playing - let presStatusBackUp = BoolCast(this.curPresentation.presStatus); - runInAction(() => this.presStatus = presStatusBackUp); - } - - /** - * This is the function that is called to retrieve the groups that have been stored and - * push them to the groupMappings. - */ - retrieveGroupMappings = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = castedKey; - }); - } - if (castedGrouping !== undefined && castedKey !== undefined) { - this.groupMappings.set(castedKey, castedGrouping); - } - }); - } - } - - //observable means render is re-called every time variable is changed - @observable - collapsed: boolean = false; - closePresentation = action(() => this.curPresentation.width = 0); - next = async () => { - const current = NumCast(this.curPresentation.selectedDoc); - //asking to get document at current index - let docAtCurrentNext = await this.getDocAtIndex(current + 1); - if (docAtCurrentNext === undefined) { - return; - } - //asking for it's presentation id - let curNextPresId = StrCast(docAtCurrentNext.presentId); - let nextSelected = current + 1; - - //if curDoc is in a group, selection slides until last one, if not it's next one - if (this.groupMappings.has(curNextPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!; - nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext); - - //end of grup so go beyond - if (nextSelected === current) nextSelected = current + 1; - } - - this.gotoDocument(nextSelected, current); - - } - back = async () => { - const current = NumCast(this.curPresentation.selectedDoc); - //requesting for the doc at current index - let docAtCurrent = await this.getDocAtIndex(current); - if (docAtCurrent === undefined) { - return; - } - - //asking for its presentation id. - let curPresId = StrCast(docAtCurrent.presentId); - let prevSelected = current - 1; - let zoomOut: boolean = false; - - //checking if this presentation id is mapped to a group, if so chosing the first element in group - if (this.groupMappings.has(curPresId)) { - let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!; - prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1; - //end of grup so go beyond - if (prevSelected === current) prevSelected = current - 1; - - //checking if any of the group members had used zooming in - currentsArray.forEach((doc: Doc) => { - //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc); - if (this.presElementsMappings.get(doc)!.selected[buttonIndex.Show]) { - zoomOut = true; - return; - } - }); - - } - - // if a group set that flag to zero or a single element - //If so making sure to zoom out, which goes back to state before zooming action - if (current > 0) { - if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.selected[buttonIndex.Show]) { - let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); - if (prevScale !== undefined) { - if (prevScale !== curScale) { - DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); - } - } - } - } - this.gotoDocument(prevSelected, current); - - } - - /** - * This is the method that checks for the actions that need to be performed - * after the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - showAfterPresented = (index: number) => { - this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; - //the order of cases is aligned based on priority - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(key) <= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(key) < index) { - key.opacity = 0; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(key) < index) { - key.opacity = 0.5; - } - } - }); - } - - /** - * This is the method that checks for the actions that need to be performed - * before the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - hideIfNotPresented = (index: number) => { - this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { - let selectedButtons: boolean[] = presElem.selected; - - //the order of cases is aligned based on priority - - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(key) >= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(key) >= index) { - key.opacity = 1; - } - } - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(key) > index) { - key.opacity = 0; - } - } - }); - } - - /** - * This method makes sure that cursor navigates to the element that - * has the option open and last in the group. If not in the group, and it has - * te option open, navigates to that element. - */ - navigateToElement = async (curDoc: Doc, fromDoc: number) => { - let docToJump: Doc = curDoc; - let curDocPresId = StrCast(curDoc.presentId, null); - let willZoom: boolean = false; - - //checking if in group - if (curDocPresId !== undefined) { - if (this.groupMappings.has(curDocPresId)) { - let currentDocGroup = this.groupMappings.get(curDocPresId)!; - currentDocGroup.forEach((doc: Doc, index: number) => { - let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected; - if (selectedButtons[buttonIndex.Navigate]) { - docToJump = doc; - willZoom = false; - } - if (selectedButtons[buttonIndex.Show]) { - docToJump = doc; - willZoom = true; - } - }); - } - - } - //docToJump stayed same meaning, it was not in the group or was the last element in the group - if (docToJump === curDoc) { - //checking if curDoc has navigation open - let curDocButtons = this.presElementsMappings.get(curDoc)!.selected; - if (curDocButtons[buttonIndex.Navigate]) { - this.jumpToTabOrRight(curDocButtons, curDoc); - } else if (curDocButtons[buttonIndex.Show]) { - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); - if (curDocButtons[buttonIndex.OpenRight]) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(curDoc, true); - } else { - await DocumentManager.Instance.jumpToDocument(curDoc, true, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - - let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); - curDoc.viewScale = newScale; - - //saving the scale user was on before zooming in - if (curScale !== 1) { - this.childrenDocs[fromDoc].viewScale = curScale; - } - - } - return; - } - let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); - let curDocButtons = this.presElementsMappings.get(docToJump)!.selected; - - - if (curDocButtons[buttonIndex.OpenRight]) { - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom); - } else { - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); - curDoc.viewScale = newScale; - //saving the scale that user was on - if (curScale !== 1) { - this.childrenDocs[fromDoc].viewScale = curScale; - } - - } - - /** - * This function checks if right option is clicked on a presentation element, if not it does open it as a tab - * with help of CollectionDockingView. - */ - jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => { - if (curDocButtons[buttonIndex.OpenRight]) { - DocumentManager.Instance.jumpToDocument(curDoc, false); - } else { - DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); - } - } - - /** - * Async function that supposedly return the doc that is located at given index. - */ - getDocAtIndex = async (index: number) => { - const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (!list) { - return undefined; - } - if (index < 0 || index >= list.length) { - return undefined; - } - - this.curPresentation.selectedDoc = index; - //awaiting async call to finish to get Doc instance - const doc = await list[index]; - return doc; - } - - /** - * The function that removes a doc from a presentation. It also makes sure to - * do necessary updates to backUps and mappings stored locally. - */ - @action - public RemoveDoc = async (index: number) => { - const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (value) { - let removedDoc = await value.splice(index, 1)[0]; - - //removing the Presentation Element stored for it - this.presElementsMappings.delete(removedDoc); - - let removedDocPresentId = StrCast(removedDoc.presentId); - - //Removing it from local mapping of the groups - if (this.groupMappings.has(removedDocPresentId)) { - let removedDocsGroup = this.groupMappings.get(removedDocPresentId); - if (removedDocsGroup) { - removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1); - if (removedDocsGroup.length === 0) { - this.groupMappings.delete(removedDocPresentId); - } - } - } - - - let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - if (castedList) { - for (let doc of castedList) { - let curDoc = await doc; - let curDocId = StrCast(curDoc.docId); - if (curDocId === removedDoc[Id]) { - castedList.splice(castedList.indexOf(curDoc), 1); - break; - - } - } - } - - //removing it from the backup of groups - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedKey = StrCast(groupDoc.presentIdStore, null); - if (castedKey === removedDocPresentId) { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1); - if (castedGrouping.length === 0) { - castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1); - } - } - } - - }); - - } - - - } - } - - /** - * An alternative remove method that removes a doc from presentation by its actual - * reference. - */ - public removeDocByRef = (doc: Doc) => { - let indexOfDoc = this.childrenDocs.indexOf(doc); - const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (value) { - value.splice(indexOfDoc, 1)[0]; - } - if (indexOfDoc !== - 1) { - return true; - } - return false; - } - - //The function that is called when a document is clicked or reached through next or back. - //it'll also execute the necessary actions if presentation is playing. - @action - public gotoDocument = async (index: number, fromDoc: number) => { - const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); - if (!list) { - return; - } - if (index < 0 || index >= list.length) { - return; - } - this.curPresentation.selectedDoc = index; - - if (!this.presStatus) { - this.presStatus = true; - this.startPresentation(index); - } - - const doc = await list[index]; - if (this.presStatus) { - this.navigateToElement(doc, fromDoc); - this.hideIfNotPresented(index); - this.showAfterPresented(index); - } - - } - - //Function that is called to resetGroupIds, so that documents get new groupIds at - //first load, when presentation is changed. - resetGroupIds = async () => { - let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); - if (castedGroupDocs !== undefined) { - castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { - let castedGrouping = await DocListCastAsync(groupDoc.grouping); - if (castedGrouping) { - castedGrouping.forEach((doc: Doc) => { - doc.presentId = Utils.GenerateGuid(); - }); - } - }); - } - runInAction(() => this.groupMappings = new Map()); - } - - /** - * Adds a document to the presentation view - **/ - @undoBatch - @action - public PinDoc(doc: Doc) { - //add this new doc to props.Document - const data = Cast(this.curPresentation.data, listSpec(Doc)); - if (data) { - data.push(doc); - } else { - this.curPresentation.data = new List([doc]); - } - - this.toggle(true); - } - - //Function that sets the store of the children docs. - @action - setChildrenDocs = (docList: Doc[]) => { - this.childrenDocs = docList; - } - - //The function that is called to render the play or pause button depending on - //if presentation is running or not. - renderPlayPauseButton = () => { - if (this.presStatus) { - return ; - } else { - return ; - } - } - - //The function that starts or resets presentaton functionally, depending on status flag. - @action - startOrResetPres = async () => { - if (this.presStatus) { - this.resetPresentation(); - } else { - this.presStatus = true; - let startIndex = await this.findStartDocument(); - this.startPresentation(startIndex); - const current = NumCast(this.curPresentation.selectedDoc); - this.gotoDocument(startIndex, current); - } - this.curPresentation.presStatus = this.presStatus; - } - - /** - * This method is called to find the start document of presentation. So - * that when user presses on play, the correct presentation element will be - * selected. - */ - findStartDocument = async () => { - let docAtZero = await this.getDocAtIndex(0); - if (docAtZero === undefined) { - return 0; - } - let docAtZeroPresId = StrCast(docAtZero.presentId); - - if (this.groupMappings.has(docAtZeroPresId)) { - let group = this.groupMappings.get(docAtZeroPresId)!; - let lastDoc = group[group.length - 1]; - return this.childrenDocs.indexOf(lastDoc); - } else { - return 0; - } - } - - //The function that resets the presentation by removing every action done by it. It also - //stops the presentaton. - @action - resetPresentation = () => { - this.childrenDocs.forEach((doc: Doc) => { - doc.opacity = 1; - doc.viewScale = 1; - }); - this.curPresentation.selectedDoc = 0; - this.presStatus = false; - this.curPresentation.presStatus = this.presStatus; - if (this.childrenDocs.length === 0) { - return; - } - DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1); - } - - - //The function that starts the presentation, also checking if actions should be applied - //directly at start. - startPresentation = (startIndex: number) => { - let selectedButtons: boolean[]; - this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => { - selectedButtons = component.selected; - if (selectedButtons[buttonIndex.HideTillPressed]) { - if (this.childrenDocs.indexOf(doc) > startIndex) { - doc.opacity = 0; - } - - } - if (selectedButtons[buttonIndex.HideAfter]) { - if (this.childrenDocs.indexOf(doc) < startIndex) { - doc.opacity = 0; - } - } - if (selectedButtons[buttonIndex.FadeAfter]) { - if (this.childrenDocs.indexOf(doc) < startIndex) { - doc.opacity = 0.5; - } - } - - }); - - } - - /** - * The function that is called to add a new presentation to the presentationView. - * It sets up te mappings and local copies of it. Resets the groupings and presentation. - * Makes the new presentation current selected, and retrieve the back-Ups if present. - */ - @action - addNewPresentation = (presTitle: string) => { - //creating a new presentation doc - let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle }); - this.props.Documents.push(newPresentationDoc); - - //setting that new doc as current - this.curPresentation = newPresentationDoc; - - //storing the doc in local copies for easier access - let newGuid = Utils.GenerateGuid(); - this.presentationsMapping.set(newGuid, newPresentationDoc); - this.presentationsKeyMapping.set(newPresentationDoc, newGuid); - - //resetting the previous presentation's actions so that new presentation can be loaded. - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = newGuid; - this.setPresentationBackUps(); - - } - - /** - * The function that is called to change the current selected presentation. - * Changes the presentation, also resetting groupings and presentation in process. - * Plus retrieving the backUps for the newly selected presentation. - */ - @action - getSelectedPresentation = (e: React.ChangeEvent) => { - //get the guid of the selected presentation - let selectedGuid = e.target.value; - //set that as current presentation - this.curPresentation = this.presentationsMapping.get(selectedGuid)!; - - //reset current Presentations local things so that new one can be loaded - this.resetGroupIds(); - this.resetPresentation(); - this.presElementsMappings = new Map(); - this.currentSelectedPresValue = selectedGuid; - this.setPresentationBackUps(); - - - } - - /** - * The function that is called to render either select for presentations, or title inputting. - */ - renderSelectOrPresSelection = () => { - let presentationList = DocListCast(this.props.Documents); - if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { - return this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; - } else { - return ; - } - } - - /** - * The function that is called on enter press of title input. It gives the - * new presentation the title user entered. If nothing is entered, gives a default title. - */ - @action - submitPresentationTitle = (e: React.KeyboardEvent) => { - if (e.keyCode === 13) { - let presTitle = this.titleInputElement!.value; - this.titleInputElement!.value = ""; - if (this.PresTitleInputOpen) { - if (presTitle === "") { - presTitle = "Presentation"; - } - this.PresTitleInputOpen = false; - this.addNewPresentation(presTitle); - } else if (this.PresTitleChangeOpen) { - this.PresTitleChangeOpen = false; - this.changePresentationTitle(presTitle); - } - } - } - - /** - * The function that is called to remove a presentation from all its copies, and the main Container's - * list. Sets up the next presentation as current. - */ - @action - removePresentation = async () => { - if (this.presentationsMapping.size !== 1) { - let presentationList = Cast(this.props.Documents, listSpec(Doc)); - let batch = UndoManager.StartBatch("presRemoval"); - - //getting the presentation that will be removed - let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!); - //that presentation is removed - presentationList!.splice(presentationList!.indexOf(removedDoc!), 1); - - //its mappings are removed from local copies - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(this.currentSelectedPresValue!); - - //the next presentation is set as current - let remainingPresentations = this.presentationsMapping.values(); - let nextDoc = remainingPresentations.next().value; - this.curPresentation = nextDoc; - - - //Storing these for being able to undo changes - let curGuid = this.currentSelectedPresValue!; - let curPresStatus = this.presStatus; - - //resetting the groups and presentation actions so that next presentation gets loaded - this.resetGroupIds(); - this.resetPresentation(); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - //Storing for undo - let currentGroups = this.groupMappings; - let curPresElemMapping = this.presElementsMappings; - - //Event to undo actions that are not related to doc directly, aka. local things - UndoManager.AddEvent({ - undo: action(() => { - this.curPresentation = removedDoc!; - this.presentationsMapping.set(curGuid, removedDoc!); - this.presentationsKeyMapping.set(removedDoc!, curGuid); - this.currentSelectedPresValue = curGuid; - - this.presStatus = curPresStatus; - this.groupMappings = currentGroups; - this.presElementsMappings = curPresElemMapping; - this.setPresentationBackUps(); - - }), - redo: action(() => { - this.curPresentation = nextDoc; - this.presStatus = false; - this.presentationsKeyMapping.delete(removedDoc!); - this.presentationsMapping.delete(curGuid); - this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString(); - this.setPresentationBackUps(); - - }), - }); - - batch.end(); - } - } - - /** - * The function that is called to change title of presentation to what user entered. - */ - @undoBatch - changePresentationTitle = (newTitle: string) => { - if (newTitle === "") { - return; - } - this.curPresentation.title = newTitle; - } - - /** - * On pointer down element that is catched on resizer of te - * presentation view. Sets up the event listeners to change the size with - * mouse move. - */ - _downsize = 0; - onPointerDown = (e: React.PointerEvent) => { - this._downsize = e.clientX; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - e.preventDefault(); - } - /** - * Changes the size of the presentation view, with mouse move. - * Minimum size is set to 300, so that every button is visible. - */ - @action - onPointerMove = (e: PointerEvent) => { - - this.curPresentation.width = Math.max(window.innerWidth - e.clientX, presMinWidth); - } - - /** - * The method that is called on pointer up event. It checks if the button is just - * clicked so that presentation view will be closed. The way it's done is to check - * for minimal pixel change like 4, and accept it as it's just a click on top of the dragger. - */ - @action - onPointerUp = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._downsize) < 4) { - let presWidth = NumCast(this.curPresentation.width); - if (presWidth - presMinWidth !== 0) { - this.curPresentation.width = 0; - } - if (presWidth === 0) { - this.curPresentation.width = presMinWidth; - } - } - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - - /** - * This function is a setter that opens up the - * presentation mode, by setting it's render flag - * to true. It also closes the presentation view. - */ - @action - openPresMode = () => { - if (!this.presMode) { - this.curPresentation.width = 0; - this.presMode = true; - } - } - - /** - * This function closes the presentation mode by setting its - * render flag to false. It also opens up the presentation view. - * By setting it to it's minimum size. - */ - @action - closePresMode = () => { - if (this.presMode) { - this.presMode = false; - this.curPresentation.width = presMinWidth; - } - - } - - /** - * Function that is called to render the presentation mode, depending on its flag. - */ - renderPresMode = () => { - if (this.presMode) { - return ; - } else { - return (null); - } - - } - - render() { - - let width = NumCast(this.curPresentation.width); - - return ( -
    -
    !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflowY: "scroll", overflowX: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> -
    - {this.renderSelectOrPresSelection()} - - - - - -
    -
    - - {this.renderPlayPauseButton()} - -
    - - this.presElementsMappings.clear()} - /> - ) => { - this.persistOpacity = e.target.checked; - this.opacity = this.persistOpacity ? 1 : 0.4; - })} - checked={this.persistOpacity} - style={{ position: "absolute", bottom: 5, left: 5 }} - onPointerEnter={action(() => this.labelOpacity = 1)} - onPointerLeave={action(() => this.labelOpacity = 0)} - /> -

    opacity {this.persistOpacity ? "persistent" : "on focus"}

    -
    -
    - -
    - {this.renderPresMode()} - -
    - ); - } -} diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 4bb2ed55b..41fc49c2e 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -203,6 +203,7 @@ export class SearchItem extends React.Component { removeDocument={returnFalse} ScreenToLocalTransform={Transform.Identity} addDocTab={returnFalse} + pinToPres={returnFalse} renderDepth={1} PanelWidth={returnXDimension} PanelHeight={returnYDimension} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index a8b616565..31f1f7a12 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,19 +1,18 @@ -import { observable, action, runInAction, ObservableMap } from "mobx"; -import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr"; -import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper"; +import { observable, ObservableMap, runInAction } from "mobx"; +import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; -import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util"; -import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types"; -import { listSpec } from "./Schema"; -import { ObjectField } from "./ObjectField"; -import { RefField, FieldId } from "./RefField"; -import { ToScriptString, SelfProxy, Parent, OnUpdate, Self, HandleUpdate, Update, Id, Copy } from "./FieldSymbols"; -import { scriptingGlobal, CompileScript, Scripting } from "../client/util/Scripting"; -import { List } from "./List"; import { DocumentType } from "../client/documents/DocumentTypes"; -import { ComputedField, ScriptField } from "./ScriptField"; +import { CompileScript, Scripting, scriptingGlobal } from "../client/util/Scripting"; +import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, Update } from "./FieldSymbols"; +import { List } from "./List"; +import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; -//import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; +import { FieldId, RefField } from "./RefField"; +import { listSpec } from "./Schema"; +import { ComputedField } from "./ScriptField"; +import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -72,6 +71,7 @@ export const HeightSym = Symbol("Height"); export const UpdatingFromServer = Symbol("UpdatingFromServer"); const CachedUpdates = Symbol("Cached updates"); + function fetchProto(doc: Doc) { const proto = doc.proto; if (proto instanceof Promise) { @@ -151,10 +151,10 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - + public static CurrentUserEmail: string = ""; public async [HandleUpdate](diff: any) { const set = diff.$set; - const sameAuthor = this.author === "foo@bar.com";//CurrentUserUtils.email; + const sameAuthor = this.author === Doc.CurrentUserEmail; if (set) { for (const key in set) { if (!key.startsWith("fields.")) { @@ -327,14 +327,12 @@ export namespace Doc { return Array.from(results); } - export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean) { + export function AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { if (target[key] === undefined) { - console.log("target key undefined"); Doc.GetProto(target)[key] = new List(); } let list = Cast(target[key], listSpec(Doc)); if (list) { - console.log("has list"); if (allowDuplicates !== true) { let pind = list.reduce((l, d, i) => d instanceof Doc && Doc.AreProtosEqual(d, doc) ? i : l, -1); if (pind !== -1) { @@ -342,15 +340,18 @@ export namespace Doc { } } if (first) { - console.log("is first"); list.splice(0, 0, doc); } else { - console.log("not first"); let ind = relativeTo ? list.indexOf(relativeTo) : -1; - if (ind === -1) list.push(doc); - else list.splice(before ? ind : ind + 1, 0, doc); - console.log("index", ind); + if (ind === -1) { + if (reversed) list.splice(0, 0, doc); + else list.push(doc); + } + else { + if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); + else list.splice(before ? ind : ind + 1, 0, doc); + } } } return true; @@ -595,10 +596,14 @@ export namespace Doc { }); } - export class DocBrush { + + export class DocData { + @observable _user_doc: Doc = undefined!; @observable BrushedDoc: ObservableMap = new ObservableMap(); } - const manager = new DocBrush(); + const manager = new DocData(); + export function UserDoc(): Doc { return manager._user_doc; } + export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { return manager.BrushedDoc.has(doc) || manager.BrushedDoc.has(Doc.GetDataDoc(doc)); } diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index c546e2aac..04194509c 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -7,7 +7,6 @@ import { ObjectField } from "./ObjectField"; import { action } from "mobx"; import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; -import { CurrentUserUtils } from "../server/authentication/models/current_user_utils"; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -61,7 +60,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } const writeMode = DocServer.getFieldWriteMode(prop as string); const fromServer = target[UpdatingFromServer]; - const sameAuthor = fromServer || (receiver.author === CurrentUserUtils.email); + const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail); const writeToDoc = sameAuthor || (writeMode !== DocServer.WriteMode.LiveReadonly); const writeToServer = sameAuthor || (writeMode === DocServer.WriteMode.Default); if (writeToDoc) { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index f36f5b73d..508655605 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -10,20 +10,17 @@ import { CollectionView } from "../../../client/views/collections/CollectionView import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { RouteStore } from "../../RouteStore"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; +import { RouteStore } from "../../RouteStore"; export class CurrentUserUtils { - private static curr_email: string; private static curr_id: string; - @observable private static user_document: Doc; //TODO tfs: these should be temporary... private static mainDocId: string | undefined; - public static get email() { return this.curr_email; } public static get id() { return this.curr_id; } - @computed public static get UserDocument() { return this.user_document; } + @computed public static get UserDocument() { return Doc.UserDoc(); } public static get MainDocId() { return this.mainDocId; } public static set MainDocId(id: string | undefined) { this.mainDocId = id; } @@ -32,7 +29,7 @@ export class CurrentUserUtils { doc.viewType = CollectionViewType.Tree; doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); - doc.title = this.email; + doc.title = Doc.CurrentUserEmail this.updateUserDocument(doc); doc.data = new List(); doc.gridGap = 5; @@ -58,6 +55,12 @@ export class CurrentUserUtils { recentlyClosed.boxShadow = "0 0"; doc.recentlyClosed = recentlyClosed; } + if (doc.curPresentation === undefined) { + const curPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation" }); + curPresentation.excludeFromLibrary = true; + curPresentation.boxShadow = "0 0"; + doc.curPresentation = curPresentation; + } if (doc.sidebar === undefined) { const sidebar = Docs.Create.StackingDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Sidebar" }); sidebar.excludeFromLibrary = true; @@ -85,15 +88,15 @@ export class CurrentUserUtils { public static async loadUserDocument({ id, email }: { id: string, email: string }) { this.curr_id = id; - this.curr_email = email; + Doc.CurrentUserEmail = email; await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => { if (id) { return DocServer.GetRefField(id).then(async field => { if (field instanceof Doc) { await this.updateUserDocument(field); - runInAction(() => this.user_document = field); + runInAction(() => Doc.SetUserDoc(field)); } else { - runInAction(() => this.user_document = this.createUserDocument(id)); + runInAction(() => Doc.SetUserDoc(this.createUserDocument(id))); } }); } else { -- cgit v1.2.3-70-g09d2 From 186cf33322b6c96386c6e9fdb823025d09c5caeb Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 11:35:18 -0400 Subject: extended treeview functionality - better indenting, state management, drag dropping. --- src/client/views/EditableView.tsx | 28 +++++--- .../views/collections/CollectionTreeView.tsx | 83 +++++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 1 - .../authentication/models/current_user_utils.ts | 5 +- 4 files changed, 75 insertions(+), 42 deletions(-) (limited to 'src/server') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index c3612fee9..dd5395802 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -3,6 +3,7 @@ import { observer } from 'mobx-react'; import { observable, action, trace } from 'mobx'; import "./EditableView.scss"; import * as Autosuggest from 'react-autosuggest'; +import { undoBatch } from '../util/UndoManager'; export interface EditableProps { /** @@ -70,14 +71,12 @@ export class EditableView extends React.Component { onKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Tab") { e.stopPropagation(); + this.finalizeEdit(e.currentTarget.value, e.shiftKey); this.props.OnTab && this.props.OnTab(); } else if (e.key === "Enter") { e.stopPropagation(); if (!e.ctrlKey) { - if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) { - this._editing = false; - this.props.isEditingCallback && this.props.isEditingCallback(false); - } + this.finalizeEdit(e.currentTarget.value, e.shiftKey); } else if (this.props.OnFillDown) { this.props.OnFillDown(e.currentTarget.value); this._editing = false; @@ -100,6 +99,14 @@ export class EditableView extends React.Component { e.stopPropagation(); } + @action + private finalizeEdit(value: string, shiftDown: boolean) { + if (this.props.SetValue(value, shiftDown)) { + this._editing = false; + this.props.isEditingCallback && this.props.isEditingCallback(false); + } + } + stopPropagation(e: React.SyntheticEvent) { e.stopPropagation(); } @@ -118,7 +125,7 @@ export class EditableView extends React.Component { className: "editableView-input", onKeyDown: this.onKeyDown, autoFocus: true, - onBlur: action(() => this._editing = false), + onBlur: e => this.finalizeEdit(e.currentTarget.value, false), onPointerDown: this.stopPropagation, onClick: this.stopPropagation, onPointerUp: this.stopPropagation, @@ -126,9 +133,14 @@ export class EditableView extends React.Component { onChange: this.props.autosuggestProps.onChange }} /> - : { this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false); })} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} - style={{ display: this.props.display, fontSize: this.props.fontSize }} />; + : this.finalizeEdit(e.currentTarget.value, false)} + onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} + style={{ display: this.props.display, fontSize: this.props.fontSize }} + />; } else { if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue(); return ( diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ebd385743..e1eb9e1a5 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -49,6 +49,8 @@ export interface TreeViewProps { treeViewId: string; parentKey: string; active: () => boolean; + showHeaderFields: () => boolean; + preventTreeViewOpen: boolean; } library.add(faTrashAlt); @@ -65,7 +67,12 @@ library.add(faArrowsAltH); library.add(faPlus, faMinus); @observer /** - * Component that takes in a document prop and a boolean whether it's collapsed or not. + * Renders a treeView of a collection of documents + * + * special fields: + * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden + * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) + * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ class TreeView extends React.Component { static loadId = ""; @@ -73,7 +80,9 @@ class TreeView extends React.Component { private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; } - @observable _collapsed: boolean = true; + @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state + @computed get treeViewOpen() { return (BoolCast(this.props.document.treeViewOpen) && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; } + set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; } @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @@ -146,7 +155,7 @@ class TreeView extends React.Component { let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); let before = x[1] < bounds[1]; - let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed); + let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen); this._header!.current!.className = "treeViewItem-header"; if (inside) this._header!.current!.className += " treeViewItem-header-inside"; else if (before) this._header!.current!.className += " treeViewItem-header-above"; @@ -163,15 +172,15 @@ class TreeView extends React.Component { fontStyle={style} fontSize={12} GetValue={() => StrCast(this.props.document[key])} - SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true} - OnFillDown={(value: string) => { + SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)} + OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.dataDoc)[key] = value; let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; return this.props.addDocument(doc); - }} - OnTab={() => this.props.indentDocument && this.props.indentDocument()} + })} + OnTab={() => { TreeView.loadId = ""; this.props.indentDocument && this.props.indentDocument(); }} />) onWorkspaceContextMenu = (e: React.MouseEvent): void => { @@ -200,7 +209,7 @@ class TreeView extends React.Component { let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); let before = x[1] < bounds[1]; - let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed); + let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen); if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.document; @@ -256,14 +265,14 @@ class TreeView extends React.Component { let rows: JSX.Element[] = []; for (let key of Object.keys(ids).slice().sort()) { let contents = doc[key]; - let contentElement: JSX.Element[] | JSX.Element = []; + let contentElement: (JSX.Element | null)[] | JSX.Element = []; if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { let remDoc = (doc: Doc) => this.remove(doc, key); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true)); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen); } else { contentElement = { const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { let remDoc = (doc: Doc) => this.remove(doc, expandKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true)); let docs = expandKey === "links" ? this.childLinks : this.childDocs; return
      {!docs ? (null) : TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)}
    ; } else if (this.treeViewExpandedView === "fields") { return
      @@ -329,8 +338,8 @@ class TreeView extends React.Component { @computed get renderBullet() { - return
      this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> - {} + return
      this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + {}
      ; } /** @@ -344,13 +353,13 @@ class TreeView extends React.Component { let headerElements = ( { - if (!this._collapsed) { + if (this.treeViewOpen) { this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : this.treeViewExpandedView === "layout" && this.props.document.links ? "links" : this.childDocs ? this.fieldKey : "fields"; } - this._collapsed = false; + this.treeViewOpen = true; })}> {this.treeViewExpandedView} ); @@ -368,7 +377,7 @@ class TreeView extends React.Component { }} > {this.editableView("title")}
      - {headerElements} + {this.props.showHeaderFields() ? headerElements : (null)} {openRight} ; } @@ -381,13 +390,13 @@ class TreeView extends React.Component { {this.renderTitle}
      - {this._collapsed ? (null) : this.renderContent} + {!this.treeViewOpen ? (null) : this.renderContent}
    ; } public static GetChildElements( - docs: Doc[], + docList: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -402,8 +411,11 @@ class TreeView extends React.Component { outerXf: () => { translateX: number, translateY: number }, active: () => boolean, panelWidth: () => number, - renderDepth: number + renderDepth: number, + showHeaderFields: () => boolean, + preventTreeViewOpen: boolean ) { + let docs = docList.filter(child => !child.excludeFromLibrary); let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { let script = viewSpecScript.script; @@ -418,7 +430,7 @@ class TreeView extends React.Component { }); } - let descending = BoolCast(containingCollection.stackingHeadersSortDescending); + let descending = BoolCast(containingCollection.stackingHeadersSortDescending, true); docs.slice().sort(function (a, b): 1 | -1 { let descA = descending ? b : a; let descB = descending ? a : b; @@ -448,11 +460,14 @@ class TreeView extends React.Component { } let indent = i === 0 ? undefined : () => { - if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) { + if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1]; let fieldKey = fieldKeysub.split("\"")[1]; - Doc.AddDocToList(docs[i - 1], fieldKey, child); - remove(child); + if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) { + Doc.AddDocToList(docs[i - 1], fieldKey, child); + docs[i - 1].treeViewOpen = true; + remove(child); + } } }; let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { @@ -481,7 +496,9 @@ class TreeView extends React.Component { ScreenToLocalTransform={screenToLocalXf} outerXf={outerXf} parentKey={key} - active={active} />; + active={active} + showHeaderFields={showHeaderFields} + preventTreeViewOpen={preventTreeViewOpen} />; }); } } @@ -561,7 +578,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { render() { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; - let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, !BoolCast(this.props.Document.stackingHeadersSortDescending)); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true)); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
    StrCast(this.resolvedDataDoc.title)} - SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true} - OnFillDown={(value: string) => { + SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} + OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.props.Document).title = value; let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; - Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, !BoolCast(this.props.Document.stackingHeadersSortDescending)); - }} /> + Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true)); + })} /> {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)} {this.props.Document.allowClear ? this.renderClearButton : (null)}
      { TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth) + moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, + this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled", + BoolCast(this.props.Document.preventTreeViewOpen)) }
    diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 606e8edb0..3f3360eff 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -736,7 +736,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { - console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); let xf = this._ref.current!.getBoundingClientRect(); let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 508655605..aa3da0034 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -10,7 +10,7 @@ import { CollectionView } from "../../../client/views/collections/CollectionView import { Doc } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { RouteStore } from "../../RouteStore"; @@ -49,12 +49,14 @@ export class CurrentUserUtils { workspaces.boxShadow = "0 0"; doc.workspaces = workspaces; } + PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); recentlyClosed.excludeFromLibrary = true; recentlyClosed.boxShadow = "0 0"; doc.recentlyClosed = recentlyClosed; } + PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => recent && (recent.preventTreeViewOpen = true)); if (doc.curPresentation === undefined) { const curPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation" }); curPresentation.excludeFromLibrary = true; @@ -73,6 +75,7 @@ export class CurrentUserUtils { } StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); doc.width = 100; + doc.preventTreeViewOpen = true; } public static loadCurrentUser() { -- cgit v1.2.3-70-g09d2 From 43d731ef2a6b2bd3fcdb7dd26fb6a8beac8e1306 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 16:23:38 -0400 Subject: from last with presentations and small tweaks. --- src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/collections/CollectionStackingView.tsx | 4 ++-- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 4 ++-- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 7 ++++--- src/client/views/presentationview/PresentationElement.tsx | 2 -- src/client/views/presentationview/PresentationView.scss | 4 ---- src/server/authentication/models/current_user_utils.ts | 2 +- 8 files changed, 11 insertions(+), 16 deletions(-) (limited to 'src/server') diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 4d696e37a..ec628c5a3 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -172,7 +172,7 @@ export class MetadataEntryMenu extends React.Component{
      - {this._allSuggestions.sort().map(s =>
    • { this._currentKey = s; this.previewValue(); })} >{s}
    • )} + {this._allSuggestions.slice().sort().map(s =>
    • { this._currentKey = s; this.previewValue(); })} >{s}
    • )}
    diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index c74c60d8f..be6ee1756 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -291,10 +291,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); let height = () => this.getDocHeight(layoutDoc); - let dxf = () => this.getDocTransform(layoutDoc, dref.current!); + let dxf = () => this.getDocTransform(layoutDoc!, dref.current!); let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); this._docXfs.push({ dxf: dxf, width: width, height: height }); - return
    + return !layoutDoc ? (null) :
    {this.getDisplayDoc(layoutDoc, d, dxf, width)}
    ; }); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index cc8476548..2536eff00 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -83,9 +83,9 @@ export class CollectionStackingViewFieldColumn extends React.Component Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns); - let height = () => parent.getDocHeight(pair!.layout); + let height = () => parent.getDocHeight(pair.layout); let dref = React.createRef(); - let dxf = () => this.getDocTransform(pair!.layout, dref.current!); + let dxf = () => this.getDocTransform(pair.layout!, dref.current!); this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 0d4b377dd..653c5c27f 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -128,7 +128,7 @@ export class KeyValueBox extends React.Component { let rows: JSX.Element[] = []; let i = 0; const self = this; - for (let key of Object.keys(ids).sort()) { + for (let key of Object.keys(ids).slice().sort()) { rows.push( { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index cf222601f..e376fbddb 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -116,9 +116,9 @@ export class PresBox extends React.Component { //FieldViewProps? let presDocs = DocListCast(this.curPresentation.data); for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) + if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { break; - + } } this.gotoDocument(nextSelected, current); @@ -239,8 +239,9 @@ export class PresBox extends React.Component { //FieldViewProps? let nextSelected = presDocs.indexOf(curDoc); let currentDocGroups: Doc[] = []; for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) + if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) { break; + } currentDocGroups.push(presDocs[nextSelected]); } diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 68c5a5b8d..83413814f 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -261,8 +261,6 @@ export default class PresentationElement extends React.Component { - //to get currently selected presentation doc - this.header!.className = "presentationView-item"; if (this.currentIndex === this.props.index) { diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index bc1899811..5c40a8808 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -23,14 +23,11 @@ user-select: none; transition: all .1s; - - .documentView-node { position: absolute; z-index: 1; } - } .presentationView-item-above { @@ -73,7 +70,6 @@ display: inline-block; width: calc(100% - 200px); letter-spacing: 2px; - } .presentation-icon { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index aa3da0034..f7ce24967 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -29,7 +29,7 @@ export class CurrentUserUtils { doc.viewType = CollectionViewType.Tree; doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); - doc.title = Doc.CurrentUserEmail + doc.title = Doc.CurrentUserEmail; this.updateUserDocument(doc); doc.data = new List(); doc.gridGap = 5; -- cgit v1.2.3-70-g09d2 From 5bd664e113434ec08fd56a34bb8f6a3e2ce136e8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 21 Aug 2019 17:25:13 -0400 Subject: fixed upload of docs zip --- .../collectionFreeForm/CollectionFreeFormView.tsx | 46 ++++++++++++++++++---- src/client/views/nodes/DocumentView.tsx | 30 -------------- src/server/index.ts | 22 ++++++----- 3 files changed, 51 insertions(+), 47 deletions(-) (limited to 'src/server') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3be6aa3d3..cbc123c61 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,17 +1,18 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, IReactionDisposer, reaction } from "mobx"; +import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; -import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { DocServer } from "../../../DocServer"; +import { Docs } from "../../../documents/Documents"; +import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -29,15 +30,14 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; -import { CollectionSubView } from "../CollectionSubView"; import { ScriptBox } from "../../ScriptBox"; +import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; +import { DocServer } from "../../../DocServer"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -841,6 +841,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); + ContextMenu.Instance.addItem({ + description: "Import document", icon: "upload", event: () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".zip"; + input.onchange = async _e => { + const files = input.files; + if (!files) return; + const file = files[0]; + let formData = new FormData(); + formData.append('file', file); + formData.append('remap', "true"); + const upload = Utils.prepend("/uploadDoc"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json === "error") { + return; + } + const doc = await DocServer.GetRefField(json); + if (!doc || !(doc instanceof Doc)) { + return; + } + const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); + doc.x = x, doc.y = y; + this.props.addDocument && + this.props.addDocument(doc, false); + }; + input.click(); + } + }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fd53262d7..8dadf2bef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,7 +40,6 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { PresBox } from './PresBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -629,35 +628,6 @@ export class DocumentView extends DocComponent(Docu } }); - cm.addItem({ - description: "Import document", icon: "upload", event: () => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; - input.onchange = async _e => { - const files = input.files; - if (!files) return; - const file = files[0]; - let formData = new FormData(); - formData.append('file', file); - formData.append('remap', "true"); - const upload = Utils.prepend("/uploadDoc"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json === "error") { - return; - } - const doc = await DocServer.GetRefField(json); - if (!doc || !(doc instanceof Doc)) { - return; - } - const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); - doc.x = x, doc.y = y; - this.props.addDocument && this.props.addDocument(doc, false); - }; - input.click(); - } - }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; diff --git a/src/server/index.ts b/src/server/index.ts index ef1829f30..51c7d8a38 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -38,7 +38,7 @@ import flash = require('connect-flash'); import { Search } from './Search'; import _ = require('lodash'); import * as Archiver from 'archiver'; -import AdmZip from 'adm-zip'; +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"; @@ -358,7 +358,7 @@ app.post("/uploadDoc", (req, res) => { for (const name in files) { const path_2 = files[name].path; const zip = new AdmZip(path_2); - zip.getEntries().forEach(entry => { + zip.getEntries().forEach((entry: any) => { if (!entry.entryName.startsWith("files/")) return; let dirname = path.dirname(entry.entryName) + "/"; let extname = path.extname(entry.entryName); @@ -367,13 +367,17 @@ app.post("/uploadDoc", (req, res) => { // zip.extractEntryTo(dirname + basename + "_s" + extname, __dirname + RouteStore.public, true, false); // zip.extractEntryTo(dirname + basename + "_m" + extname, __dirname + RouteStore.public, true, false); // zip.extractEntryTo(dirname + basename + "_l" + extname, __dirname + RouteStore.public, true, false); - zip.extractEntryTo(entry.entryName, __dirname + RouteStore.public, true, false); - dirname = "/" + dirname; - - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_o" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_s" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_m" + extname)); - fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_l" + extname)); + try { + zip.extractEntryTo(entry.entryName, __dirname + RouteStore.public, true, false); + dirname = "/" + dirname; + + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_o" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_s" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_m" + extname)); + fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_l" + extname)); + } catch (e) { + console.log(e); + } }); const json = zip.getEntry("doc.json"); let docs: any; -- cgit v1.2.3-70-g09d2 From f4888ec4b2862cdf890ac1b0f6670b41398266df Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 20:01:35 -0400 Subject: can create google slides presentations --- .../apis/google_docs/GoogleApiClientUtils.ts | 99 +++++++++++++--------- src/client/views/MainView.tsx | 4 + src/client/views/nodes/FormattedTextBox.tsx | 20 ++--- src/server/apis/google/GoogleApiServerUtils.ts | 65 +++++++++----- src/server/index.ts | 19 ++--- 5 files changed, 123 insertions(+), 84 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 821c52270..52452cd95 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -1,4 +1,4 @@ -import { docs_v1 } from "googleapis"; +import { docs_v1, slides_v1 } from "googleapis"; import { PostToServer } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { Opt } from "../../../new_fields/Doc"; @@ -9,47 +9,47 @@ export const Pushes = "googleDocsPushCount"; export namespace GoogleApiClientUtils { - export namespace Docs { + export enum Actions { + Create = "create", + Retrieve = "retrieve", + Update = "update" + } - export enum Actions { - Create = "create", - Retrieve = "retrieve", - Update = "update" - } + export enum WriteMode { + Insert, + Replace + } - export enum WriteMode { - Insert, - Replace - } + export type DocumentId = string; + export type Reference = DocumentId | CreateOptions; + export type TextContent = string | string[]; + export type IdHandler = (id: DocumentId) => any; + export type CreationResult = Opt; + export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; + export type ReadResult = { title?: string, body?: string }; - export type DocumentId = string; - export type Reference = DocumentId | CreateOptions; - export type TextContent = string | string[]; - export type IdHandler = (id: DocumentId) => any; + export interface CreateOptions { + title?: string; // if excluded, will use a default title annotated with the current date + } - export type CreationResult = Opt; - export type RetrievalResult = Opt; - export type UpdateResult = Opt; - export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; - export type ReadResult = { title?: string, body?: string }; + export interface RetrieveOptions { + documentId: DocumentId; + } - export interface CreateOptions { - handler: IdHandler; // callback to process the documentId of the newly created Google Doc - title?: string; // if excluded, will use a default title annotated with the current date - } + export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; - export interface RetrieveOptions { - documentId: DocumentId; - } + export interface WriteOptions { + mode: WriteMode; + content: TextContent; + reference: Reference; + index?: number; // if excluded, will compute the last index of the document and append the content there + } - export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; - export interface WriteOptions { - mode: WriteMode; - content: TextContent; - reference: Reference; - index?: number; // if excluded, will compute the last index of the document and append the content there - } + export namespace Docs { + + export type RetrievalResult = Opt; + export type UpdateResult = Opt; export interface UpdateOptions { documentId: DocumentId; @@ -106,7 +106,7 @@ export namespace GoogleApiClientUtils { * @returns the documentId of the newly generated document, or undefined if the creation process fails. */ export const create = async (options: CreateOptions): Promise => { - const path = RouteStore.googleDocs + Actions.Create; + const path = RouteStore.googleDocs + "Documents/" + Actions.Create; const parameters = { requestBody: { title: options.title || `Dash Export (${new Date().toDateString()})` @@ -115,17 +115,14 @@ export namespace GoogleApiClientUtils { try { const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); const generatedId = schema.documentId; - if (generatedId) { - options.handler(generatedId); - return generatedId; - } + return generatedId; } catch { return undefined; } }; export const retrieve = async (options: RetrieveOptions): Promise => { - const path = RouteStore.googleDocs + Actions.Retrieve; + const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve; try { const schema: RetrievalResult = await PostToServer(path, options); return schema; @@ -135,7 +132,7 @@ export namespace GoogleApiClientUtils { }; export const update = async (options: UpdateOptions): Promise => { - const path = RouteStore.googleDocs + Actions.Update; + const path = RouteStore.googleDocs + "Documents/" + Actions.Update; const parameters = { documentId: options.documentId, requestBody: { @@ -221,4 +218,24 @@ export namespace GoogleApiClientUtils { } + export namespace Slides { + + export const create = async (options: CreateOptions): Promise => { + const path = RouteStore.googleDocs + "Slides/" + Actions.Create; + const parameters = { + requestBody: { + title: options.title || `Dash Export (${new Date().toDateString()})` + } + }; + try { + const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters); + const generatedId = schema.presentationId; + return generatedId; + } catch { + return undefined; + } + }; + + } + } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f28844009..3490a0f68 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -40,6 +40,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import PresModeMenu from './presentationview/PresentationModeMenu'; import { PresBox } from './nodes/PresBox'; +import { GoogleApiClientUtils } from '../apis/google_docs/GoogleApiClientUtils'; @observer export class MainView extends React.Component { @@ -120,6 +121,9 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); + let title = "THIS IS MY FIRST DASH PRESENTATION"; + GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id)); + tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c8722c6e3..408a6948e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -64,7 +64,7 @@ export const GoogleRef = "googleDocId"; type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); -type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void; +type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void; @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { @@ -430,22 +430,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } pushToGoogleDoc = async () => { - this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { - let modes = GoogleApiClientUtils.Docs.WriteMode; + this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { + let modes = GoogleApiClientUtils.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; - reference = { - title: StrCast(this.dataDoc.title), - handler: id => this.dataDoc[GoogleRef] = id - }; + reference = { title: StrCast(this.dataDoc.title) }; } let redo = async () => { let data = Cast(this.dataDoc.data, RichTextField); if (this._editorView && reference && data) { let content = data[ToPlainText](); let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + response && (this.dataDoc[GoogleRef] = response.documentId); let pushSuccess = response !== undefined && !("errors" in response); dataDoc.unchanged = pushSuccess; DocumentDecorations.Instance.startPushOutcome(pushSuccess); @@ -465,14 +463,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe pullFromGoogleDoc = async (handler: PullHandler) => { let dataDoc = this.dataDoc; let documentId = StrCast(dataDoc[GoogleRef]); - let exportState: GoogleApiClientUtils.Docs.ReadResult = {}; + let exportState: GoogleApiClientUtils.ReadResult = {}; if (documentId) { exportState = await GoogleApiClientUtils.Docs.read({ documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } - updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { let pullSuccess = false; if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { const data = Cast(dataDoc.data, RichTextField); @@ -496,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DocumentDecorations.Instance.startPullOutcome(pullSuccess); } - checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => { if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { let data = Cast(dataDoc.data, RichTextField); if (data) { diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index 817b2b696..ff027c501 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -1,7 +1,10 @@ -import { google, docs_v1 } from "googleapis"; +import { google, docs_v1, slides_v1 } from "googleapis"; import { createInterface } from "readline"; import { readFile, writeFile } from "fs"; import { OAuth2Client } from "google-auth-library"; +import { Opt } from "../../../new_fields/Doc"; +import { GlobalOptions } from "googleapis-common"; +import { GaxiosResponse } from "gaxios"; /** * Server side authentication for Google Api queries. @@ -13,38 +16,57 @@ export namespace GoogleApiServerUtils { const SCOPES = [ 'documents.readonly', 'documents', + 'presentations', + 'presentations.readonly', 'drive', 'drive.file', ]; - // The file token.json stores the user's access and refresh tokens, and is - // created automatically when the authorization flow completes for the first - // time. + export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); - export namespace Docs { + export enum Sector { + Documents = "Documents", + Slides = "Slides" + } + - export interface CredentialPaths { - credentials: string; - token: string; - } + export interface CredentialPaths { + credentials: string; + token: string; + } - export type Endpoint = docs_v1.Docs; + export type ApiResponse = Promise; + export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse; + export type ApiHandler = (parameters: any) => ApiResponse; + export type Action = "create" | "retrieve" | "update"; - export const GetEndpoint = async (paths: CredentialPaths) => { - return new Promise((resolve, reject) => { - readFile(paths.credentials, (err, credentials) => { - if (err) { - reject(err); - return console.log('Error loading client secret file:', err); + export type Endpoint = { get: ApiHandler, create: ApiHandler, batchUpdate: ApiHandler }; + export type EndpointParameters = GlobalOptions & { version: "v1" }; + + export const GetEndpoint = async (sector: string, paths: CredentialPaths) => { + return new Promise>((resolve, reject) => { + readFile(paths.credentials, (err, credentials) => { + if (err) { + reject(err); + return console.log('Error loading client secret file:', err); + } + return authorize(parseBuffer(credentials), paths.token).then(auth => { + let routed: Opt; + let parameters: EndpointParameters = { auth, version: "v1" }; + switch (sector) { + case Sector.Documents: + routed = google.docs(parameters).documents; + break; + case Sector.Slides: + routed = google.slides(parameters).presentations; + break; } - return authorize(parseBuffer(credentials), paths.token).then(auth => { - resolve(google.docs({ version: "v1", auth })); - }); + resolve(routed); }); }); - }; + }); + }; - } /** * Create an OAuth2 client with the given credentials, and returns the promise resolving to the authenticated client @@ -105,5 +127,4 @@ export namespace GoogleApiServerUtils { }); }); } - } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index ef1829f30..6aecb875a 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -45,6 +45,7 @@ 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'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -799,21 +800,19 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any const credentials = path.join(__dirname, "./credentials/google_docs_credentials.json"); const token = path.join(__dirname, "./credentials/google_docs_token.json"); -type ApiResponse = Promise; -type ApiHandler = (endpoint: docs_v1.Resource$Documents, parameters: any) => ApiResponse; -type Action = "create" | "retrieve" | "update"; - -const EndpointHandlerMap = new Map([ +const EndpointHandlerMap = new Map([ ["create", (api, params) => api.create(params)], ["retrieve", (api, params) => api.get(params)], ["update", (api, params) => api.batchUpdate(params)], ]); -app.post(RouteStore.googleDocs + ":action", (req, res) => { - GoogleApiServerUtils.Docs.GetEndpoint({ credentials, token }).then(endpoint => { - let handler = EndpointHandlerMap.get(req.params.action); - if (handler) { - let execute = handler(endpoint.documents, req.body).then( +app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => { + let sector = req.params.sector; + let action = req.params.action; + GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => { + let handler = EndpointHandlerMap.get(action); + if (endpoint && handler) { + let execute = handler(endpoint, req.body).then( response => res.send(response.data), rejection => res.send(rejection) ); -- cgit v1.2.3-70-g09d2 From 30d55c5b91329d32a79b7135fff3566b2cd0e822 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 20:33:42 -0400 Subject: create is now shared between slides and docs --- .../apis/google_docs/GoogleApiClientUtils.ts | 79 +++++++++------------- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/server/RouteStore.ts | 2 +- src/server/apis/google/GoogleApiServerUtils.ts | 6 +- src/server/index.ts | 4 +- 6 files changed, 40 insertions(+), 55 deletions(-) (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index 52452cd95..ea020ed00 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -9,6 +9,11 @@ export const Pushes = "googleDocsPushCount"; export namespace GoogleApiClientUtils { + export enum Service { + Documents = "Documents", + Slides = "Slides" + } + export enum Actions { Create = "create", Retrieve = "retrieve", @@ -29,6 +34,7 @@ export namespace GoogleApiClientUtils { export type ReadResult = { title?: string, body?: string }; export interface CreateOptions { + service: Service; title?: string; // if excluded, will use a default title annotated with the current date } @@ -45,6 +51,30 @@ export namespace GoogleApiClientUtils { index?: number; // if excluded, will compute the last index of the document and append the content there } + /** + * After following the authentication routine, which connects this API call to the current signed in account + * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which + * should appear in the user's Google Doc library instantaneously. + * + * @param options the title to assign to the new document, and the information necessary + * to store the new documentId returned from the creation process + * @returns the documentId of the newly generated document, or undefined if the creation process fails. + */ + export const create = async (options: CreateOptions): Promise => { + const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Create}`; + const parameters = { + requestBody: { + title: options.title || `Dash Export (${new Date().toDateString()})` + } + }; + try { + const schema: any = await PostToServer(path, parameters); + let key = ["document", "presentation"].find(prefix => `${prefix}Id` in schema) + "Id"; + return schema[key]; + } catch { + return undefined; + } + }; export namespace Docs { @@ -96,33 +126,8 @@ export namespace GoogleApiClientUtils { } - /** - * After following the authentication routine, which connects this API call to the current signed in account - * and grants the appropriate permissions, this function programmatically creates an arbitrary Google Doc which - * should appear in the user's Google Doc library instantaneously. - * - * @param options the title to assign to the new document, and the information necessary - * to store the new documentId returned from the creation process - * @returns the documentId of the newly generated document, or undefined if the creation process fails. - */ - export const create = async (options: CreateOptions): Promise => { - const path = RouteStore.googleDocs + "Documents/" + Actions.Create; - const parameters = { - requestBody: { - title: options.title || `Dash Export (${new Date().toDateString()})` - } - }; - try { - const schema: docs_v1.Schema$Document = await PostToServer(path, parameters); - const generatedId = schema.documentId; - return generatedId; - } catch { - return undefined; - } - }; - export const retrieve = async (options: RetrieveOptions): Promise => { - const path = RouteStore.googleDocs + "Documents/" + Actions.Retrieve; + const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`; try { const schema: RetrievalResult = await PostToServer(path, options); return schema; @@ -132,7 +137,7 @@ export namespace GoogleApiClientUtils { }; export const update = async (options: UpdateOptions): Promise => { - const path = RouteStore.googleDocs + "Documents/" + Actions.Update; + const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`; const parameters = { documentId: options.documentId, requestBody: { @@ -218,24 +223,4 @@ export namespace GoogleApiClientUtils { } - export namespace Slides { - - export const create = async (options: CreateOptions): Promise => { - const path = RouteStore.googleDocs + "Slides/" + Actions.Create; - const parameters = { - requestBody: { - title: options.title || `Dash Export (${new Date().toDateString()})` - } - }; - try { - const schema: slides_v1.Schema$Presentation = await PostToServer(path, parameters); - const generatedId = schema.presentationId; - return generatedId; - } catch { - return undefined; - } - }; - - } - } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3490a0f68..2a219fdd1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -122,7 +122,7 @@ export class MainView extends React.Component { var tag = document.createElement('script'); let title = "THIS IS MY FIRST DASH PRESENTATION"; - GoogleApiClientUtils.Slides.create({ title }).then(id => console.log("We received this! ", id)); + GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id)); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 408a6948e..bcec6f7c3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -436,7 +436,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); if (!reference) { mode = modes.Insert; - reference = { title: StrCast(this.dataDoc.title) }; + reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) }; } let redo = async () => { let data = Cast(this.dataDoc.data, RichTextField); diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index 5d977006a..014906054 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -31,6 +31,6 @@ export enum RouteStore { // APIS cognitiveServices = "/cognitiveservices", - googleDocs = "/googleDocs/" + googleDocs = "/googleDocs" } \ No newline at end of file diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts index ff027c501..8785cd974 100644 --- a/src/server/apis/google/GoogleApiServerUtils.ts +++ b/src/server/apis/google/GoogleApiServerUtils.ts @@ -24,7 +24,7 @@ export namespace GoogleApiServerUtils { export const parseBuffer = (data: Buffer) => JSON.parse(data.toString()); - export enum Sector { + export enum Service { Documents = "Documents", Slides = "Slides" } @@ -54,10 +54,10 @@ export namespace GoogleApiServerUtils { let routed: Opt; let parameters: EndpointParameters = { auth, version: "v1" }; switch (sector) { - case Sector.Documents: + case Service.Documents: routed = google.docs(parameters).documents; break; - case Sector.Slides: + case Service.Slides: routed = google.slides(parameters).presentations; break; } diff --git a/src/server/index.ts b/src/server/index.ts index 6aecb875a..0476bf3df 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -806,10 +806,10 @@ const EndpointHandlerMap = new Map api.batchUpdate(params)], ]); -app.post(RouteStore.googleDocs + ":sector/:action", (req, res) => { +app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => { let sector = req.params.sector; let action = req.params.action; - GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Sector[sector], { credentials, token }).then(endpoint => { + GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { let execute = handler(endpoint, req.body).then( -- cgit v1.2.3-70-g09d2 From e68b1cc60b2a8ffe55eea3e1d5266d6897dcdcb6 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 21 Aug 2019 21:31:17 -0400 Subject: integrated slides --- .../apis/google_docs/GoogleApiClientUtils.ts | 47 +- src/client/views/MainView.tsx | 3 - src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/server/slides.json | 10820 +++++++++++++++++++ 4 files changed, 10851 insertions(+), 21 deletions(-) create mode 100644 src/server/slides.json (limited to 'src/server') diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts index ea020ed00..3e90adc4d 100644 --- a/src/client/apis/google_docs/GoogleApiClientUtils.ts +++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts @@ -25,11 +25,11 @@ export namespace GoogleApiClientUtils { Replace } - export type DocumentId = string; - export type Reference = DocumentId | CreateOptions; + export type Identifier = string; + export type Reference = Identifier | CreateOptions; export type TextContent = string | string[]; - export type IdHandler = (id: DocumentId) => any; - export type CreationResult = Opt; + export type IdHandler = (id: Identifier) => any; + export type CreationResult = Opt; export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>; export type ReadResult = { title?: string, body?: string }; @@ -39,10 +39,14 @@ export namespace GoogleApiClientUtils { } export interface RetrieveOptions { - documentId: DocumentId; + service: Service; + identifier: Identifier; } - export type ReadOptions = RetrieveOptions & { removeNewlines?: boolean }; + export interface ReadOptions { + identifier: Identifier; + removeNewlines?: boolean; + } export interface WriteOptions { mode: WriteMode; @@ -78,11 +82,11 @@ export namespace GoogleApiClientUtils { export namespace Docs { - export type RetrievalResult = Opt; + export type RetrievalResult = Opt; export type UpdateResult = Opt; export interface UpdateOptions { - documentId: DocumentId; + documentId: Identifier; requests: docs_v1.Schema$Request[]; } @@ -126,11 +130,20 @@ export namespace GoogleApiClientUtils { } + const KeyMapping = new Map([ + [Service.Documents, "documentId"], + [Service.Slides, "presentationId"] + ]); + export const retrieve = async (options: RetrieveOptions): Promise => { - const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Retrieve}`; + const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`; try { - const schema: RetrievalResult = await PostToServer(path, options); - return schema; + let parameters: any = {}, key: string | undefined; + if ((key = KeyMapping.get(options.service))) { + parameters[key] = options.identifier; + const schema: RetrievalResult = await PostToServer(path, parameters); + return schema; + } } catch { return undefined; } @@ -153,7 +166,7 @@ export namespace GoogleApiClientUtils { }; export const read = async (options: ReadOptions): Promise => { - return retrieve(options).then(document => { + return retrieve({ ...options, service: Service.Documents }).then(document => { let result: ReadResult = {}; if (document) { let title = document.title; @@ -165,7 +178,7 @@ export namespace GoogleApiClientUtils { }; export const readLines = async (options: ReadOptions): Promise => { - return retrieve(options).then(document => { + return retrieve({ ...options, service: Service.Documents }).then(document => { let result: ReadLinesResult = {}; if (document) { let title = document.title; @@ -179,14 +192,14 @@ export namespace GoogleApiClientUtils { export const write = async (options: WriteOptions): Promise => { const requests: docs_v1.Schema$Request[] = []; - const documentId = await Utils.initialize(options.reference); - if (!documentId) { + const identifier = await Utils.initialize(options.reference); + if (!identifier) { return undefined; } let index = options.index; const mode = options.mode; if (!(index && mode === WriteMode.Insert)) { - let schema = await retrieve({ documentId }); + let schema = await retrieve({ identifier, service: Service.Documents }); if (!schema || !(index = Utils.endOf(schema))) { return undefined; } @@ -212,7 +225,7 @@ export namespace GoogleApiClientUtils { if (!requests.length) { return undefined; } - let replies: any = await update({ documentId, requests }); + let replies: any = await update({ documentId: identifier, requests }); let errors = "errors"; if (errors in replies) { console.log("Write operation failed:"); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2a219fdd1..0286a201b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -121,9 +121,6 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); - let title = "THIS IS MY FIRST DASH PRESENTATION"; - GoogleApiClientUtils.create({ service: GoogleApiClientUtils.Service.Slides, title }).then(id => console.log("We received this! ", id)); - tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bcec6f7c3..a603676bb 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -465,7 +465,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let documentId = StrCast(dataDoc[GoogleRef]); let exportState: GoogleApiClientUtils.ReadResult = {}; if (documentId) { - exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId }); } UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); } diff --git a/src/server/slides.json b/src/server/slides.json new file mode 100644 index 000000000..323cac3a6 --- /dev/null +++ b/src/server/slides.json @@ -0,0 +1,10820 @@ +{ + "presentationId": "1gHxyT6bBhsPVeuWNnWDzI33yEviMVo8n60JtZiVy3tY", + "pageSize": { + "width": { + "magnitude": 9144000, + "unit": "EMU" + }, + "height": { + "magnitude": 5143500, + "unit": "EMU" + } + }, + "slides": [ + { + "objectId": "p", + "pageElements": [ + { + "objectId": "i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6842, + "translateX": 311708.35000000003, + "translateY": 744575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 20, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 20, + "textRun": { + "content": "Importing into Dash\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "CENTERED_TITLE", + "parentObjectId": "p2_i0" + } + } + }, + { + "objectId": "i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2642, + "translateX": 311700, + "translateY": 2834125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 15, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 15, + "textRun": { + "content": "By Sam Wilkins\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p2_i1" + } + } + } + ], + "slideProperties": { + "layoutObjectId": "p2", + "masterObjectId": "simple-light-2", + "notesPage": { + "objectId": "p:notes", + "pageType": "NOTES", + "pageElements": [ + { + "objectId": "i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032025, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_IMAGE", + "parentObjectId": "n:slide" + } + } + }, + { + "objectId": "i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "n:text" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + }, + "notesProperties": { + "speakerNotesObjectId": "i3" + } + } + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "g5f40953d50_0_0", + "pageElements": [ + { + "objectId": "g5f40953d50_0_1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 10, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 10, + "textRun": { + "content": "Dr. Seuss\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p4_i0" + } + } + }, + { + "objectId": "g5f40953d50_0_2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 25, + "paragraphMarker": { + "style": { + "indentStart": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 25, + "textRun": { + "content": "Here is a bulleted list!\n", + "style": {} + } + }, + { + "startIndex": 25, + "endIndex": 34, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 25, + "endIndex": 34, + "textRun": { + "content": "One fish\n", + "style": {} + } + }, + { + "startIndex": 34, + "endIndex": 43, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 34, + "endIndex": 43, + "textRun": { + "content": "Two fish\n", + "style": {} + } + }, + { + "startIndex": 43, + "endIndex": 52, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 43, + "endIndex": 52, + "textRun": { + "content": "Red fish\n", + "style": {} + } + }, + { + "startIndex": 52, + "endIndex": 62, + "paragraphMarker": { + "style": { + "indentStart": { + "magnitude": 36, + "unit": "PT" + }, + "indentFirstLine": { + "magnitude": 18, + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "kix.wifbmqnyqu4p", + "glyph": "●", + "bulletStyle": { + "underline": false + } + } + } + }, + { + "startIndex": 52, + "endIndex": 62, + "textRun": { + "content": "Blue fish\n", + "style": {} + } + } + ], + "lists": { + "kix.wifbmqnyqu4p": { + "listId": "kix.wifbmqnyqu4p", + "nestingLevel": { + "0": { + "bulletStyle": { + "underline": false + } + }, + "1": { + "bulletStyle": { + "underline": false + } + }, + "2": { + "bulletStyle": { + "underline": false + } + }, + "3": { + "bulletStyle": { + "underline": false + } + }, + "4": { + "bulletStyle": { + "underline": false + } + }, + "5": { + "bulletStyle": { + "underline": false + } + }, + "6": { + "bulletStyle": { + "underline": false + } + }, + "7": { + "bulletStyle": { + "underline": false + } + }, + "8": { + "bulletStyle": { + "underline": false + } + } + } + }, + "kix.yuy8atv38lqp": { + "listId": "kix.yuy8atv38lqp", + "nestingLevel": { + "0": { + "bulletStyle": { + "underline": false + } + }, + "1": { + "bulletStyle": { + "underline": false + } + }, + "2": { + "bulletStyle": { + "underline": false + } + }, + "3": { + "bulletStyle": { + "underline": false + } + }, + "4": { + "bulletStyle": { + "underline": false + } + }, + "5": { + "bulletStyle": { + "underline": false + } + }, + "6": { + "bulletStyle": { + "underline": false + } + }, + "7": { + "bulletStyle": { + "underline": false + } + }, + "8": { + "bulletStyle": { + "underline": false + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p4_i1" + } + } + } + ], + "slideProperties": { + "layoutObjectId": "p4", + "masterObjectId": "simple-light-2", + "notesPage": { + "objectId": "g5f40953d50_0_0:notes", + "pageType": "NOTES", + "pageElements": [ + { + "objectId": "g5f40953d50_0_3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_IMAGE", + "parentObjectId": "n:slide" + } + } + }, + { + "objectId": "g5f40953d50_0_4", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "n:text" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + }, + "notesProperties": { + "speakerNotesObjectId": "g5f40953d50_0_4" + } + } + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + } + ], + "title": "THIS IS MY FIRST DASH PRESENTATION", + "masters": [ + { + "objectId": "simple-light-2", + "pageType": "MASTER", + "pageElements": [ + { + "objectId": "p1_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK1" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 28, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "TITLE" + } + } + }, + { + "objectId": "p1_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 18, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 115, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "magnitude": 16, + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 18, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 14, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "BODY" + } + } + }, + { + "objectId": "p1_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "END", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "NEVER_COLLAPSE" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 10, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "themeColor": "DARK2" + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 10, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "SLIDE_NUMBER" + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "solidFill": { + "color": { + "themeColor": "LIGHT1" + }, + "alpha": 1 + } + }, + "colorScheme": { + "colors": [ + { + "type": "DARK1", + "color": {} + }, + { + "type": "LIGHT1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "DARK2", + "color": { + "red": 0.34901962, + "green": 0.34901962, + "blue": 0.34901962 + } + }, + { + "type": "LIGHT2", + "color": { + "red": 0.93333334, + "green": 0.93333334, + "blue": 0.93333334 + } + }, + { + "type": "ACCENT1", + "color": { + "red": 1, + "green": 0.67058825, + "blue": 0.2509804 + } + }, + { + "type": "ACCENT2", + "color": { + "red": 0.12941177, + "green": 0.12941177, + "blue": 0.12941177 + } + }, + { + "type": "ACCENT3", + "color": { + "red": 0.47058824, + "green": 0.5647059, + "blue": 0.6117647 + } + }, + { + "type": "ACCENT4", + "color": { + "red": 1, + "green": 0.67058825, + "blue": 0.2509804 + } + }, + { + "type": "ACCENT5", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "ACCENT6", + "color": { + "red": 0.93333334, + "green": 1, + "blue": 0.25490198 + } + }, + { + "type": "HYPERLINK", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "FOLLOWED_HYPERLINK", + "color": { + "green": 0.5921569, + "blue": 0.654902 + } + }, + { + "type": "TEXT1", + "color": {} + }, + { + "type": "BACKGROUND1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "TEXT2", + "color": { + "red": 0.93333334, + "green": 0.93333334, + "blue": 0.93333334 + } + }, + { + "type": "BACKGROUND2", + "color": { + "red": 0.34901962, + "green": 0.34901962, + "blue": 0.34901962 + } + } + ] + } + }, + "masterProperties": { + "displayName": "Simple Light" + } + } + ], + "layouts": [ + { + "objectId": "p2", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p2_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6842, + "translateX": 311708.35000000003, + "translateY": 744575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 52, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "CENTERED_TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p2_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2642, + "translateX": 311700, + "translateY": 2834125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 28, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p2_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE", + "displayName": "Title slide" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p3", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p3_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.2806, + "translateX": 311700, + "translateY": 2150850, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 36, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p3_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "SECTION_HEADER", + "displayName": "Section header" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p4", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p4_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p4_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p4_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_AND_BODY", + "displayName": "Title and body" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p5", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p5_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p5_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3333, + "scaleY": 1.1388, + "translateX": 311700, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p5_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3333, + "scaleY": 1.1388, + "translateX": 4832400, + "translateY": 1152475, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 14, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "index": 1, + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p5_i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_AND_TWO_COLUMNS", + "displayName": "Title and two columns" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p6", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p6_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.1909, + "translateX": 311700, + "translateY": 445025, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p6_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "TITLE_ONLY", + "displayName": "Title only" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p7", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p7_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.936, + "scaleY": 0.2519, + "translateX": 311700, + "translateY": 555600, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 24, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p7_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.936, + "scaleY": 1.0598, + "translateX": 311700, + "translateY": 1389600, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 12, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p7_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "ONE_COLUMN_TEXT", + "displayName": "One column text" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p8", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p8_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.1226, + "scaleY": 1.3636, + "translateX": 490250, + "translateY": 450150, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 48, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p8_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "MAIN_POINT", + "displayName": "Main point" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p9", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p9_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.524, + "scaleY": 1.7145, + "translateX": 4572000, + "translateY": -125, + "unit": "EMU" + }, + "shape": { + "shapeType": "RECTANGLE", + "shapeProperties": { + "shapeBackgroundFill": { + "solidFill": { + "color": { + "themeColor": "LIGHT2" + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "MIDDLE" + } + } + }, + { + "objectId": "p9_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3484, + "scaleY": 0.4941, + "translateX": 265500, + "translateY": 1233175, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 42, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p9_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.3484, + "scaleY": 0.4117, + "translateX": 265500, + "translateY": 2803075, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "CENTER", + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 21, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SUBTITLE", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p9_i3", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.279, + "scaleY": 1.2317, + "translateX": 4939500, + "translateY": 724075, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p9_i4", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "SECTION_TITLE_AND_DESCRIPTION", + "displayName": "Section title and description" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p10", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p10_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.9996, + "scaleY": 0.2017, + "translateX": 311700, + "translateY": 4230575, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "spaceBelow": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 18, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p10_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "CAPTION_ONLY", + "displayName": "Caption" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p11", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p11_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.6545, + "translateX": 311700, + "translateY": 1106125, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": " ", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "1": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "2": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "3": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "4": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "5": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "6": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "7": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + }, + "8": { + "bulletStyle": { + "fontSize": { + "magnitude": 120, + "unit": "PT" + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + }, + "contentAlignment": "BOTTOM" + }, + "placeholder": { + "type": "TITLE", + "parentObjectId": "p1_i0" + } + } + }, + { + "objectId": "p11_i1", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.8402, + "scaleY": 0.4336, + "translateX": 311700, + "translateY": 3152225, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": {} + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "alignment": "CENTER", + "direction": "LEFT_TO_RIGHT" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": {} + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": {} + }, + "1": { + "bulletStyle": {} + }, + "2": { + "bulletStyle": {} + }, + "3": { + "bulletStyle": {} + }, + "4": { + "bulletStyle": {} + }, + "5": { + "bulletStyle": {} + }, + "6": { + "bulletStyle": {} + }, + "7": { + "bulletStyle": {} + }, + "8": { + "bulletStyle": {} + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "BODY", + "parentObjectId": "p1_i1" + } + } + }, + { + "objectId": "p11_i2", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "BIG_NUMBER", + "displayName": "Big number" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + }, + { + "objectId": "p12", + "pageType": "LAYOUT", + "pageElements": [ + { + "objectId": "p12_i0", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 0.1829, + "scaleY": 0.1312, + "translateX": 8472457.8125, + "translateY": 4663216.797499999, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 2, + "paragraphMarker": { + "style": { + "direction": "LEFT_TO_RIGHT" + } + } + }, + { + "endIndex": 1, + "autoText": { + "type": "SLIDE_NUMBER", + "content": "#", + "style": {} + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": {} + } + } + ] + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "INHERIT" + }, + "outline": { + "propertyState": "INHERIT" + }, + "shadow": { + "propertyState": "INHERIT" + } + }, + "placeholder": { + "type": "SLIDE_NUMBER", + "parentObjectId": "p1_i2" + } + } + } + ], + "layoutProperties": { + "masterObjectId": "simple-light-2", + "name": "BLANK", + "displayName": "Blank" + }, + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "INHERIT" + } + } + } + ], + "locale": "en", + "revisionId": "kaHql7SEgvqFcw", + "notesMaster": { + "objectId": "n", + "pageType": "NOTES_MASTER", + "pageElements": [ + { + "objectId": "n:slide", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 2.032025, + "scaleY": 1.143, + "translateX": 381300, + "translateY": 685800, + "unit": "EMU" + }, + "shape": { + "shapeProperties": { + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID" + }, + "contentAlignment": "MIDDLE" + }, + "placeholder": { + "type": "SLIDE_IMAGE" + } + } + }, + { + "objectId": "n:text", + "size": { + "width": { + "magnitude": 3000000, + "unit": "EMU" + }, + "height": { + "magnitude": 3000000, + "unit": "EMU" + } + }, + "transform": { + "scaleX": 1.8288, + "scaleY": 1.3716, + "translateX": 685800, + "translateY": 4343400, + "unit": "EMU" + }, + "shape": { + "shapeType": "TEXT_BOX", + "text": { + "textElements": [ + { + "endIndex": 1, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "endIndex": 1, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 1, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 1, + "endIndex": 2, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 2, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 2, + "endIndex": 3, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 3, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 3, + "endIndex": 4, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 4, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 4, + "endIndex": 5, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 5, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 5, + "endIndex": 6, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 6, + "glyph": "●", + "bulletStyle": {} + } + } + }, + { + "startIndex": 6, + "endIndex": 7, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 7, + "glyph": "○", + "bulletStyle": {} + } + } + }, + { + "startIndex": 7, + "endIndex": 8, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "paragraphMarker": { + "style": { + "lineSpacing": 100, + "alignment": "START", + "indentStart": { + "unit": "PT" + }, + "indentEnd": { + "unit": "PT" + }, + "spaceAbove": { + "unit": "PT" + }, + "spaceBelow": { + "unit": "PT" + }, + "indentFirstLine": { + "unit": "PT" + }, + "direction": "LEFT_TO_RIGHT", + "spacingMode": "COLLAPSE_LISTS" + }, + "bullet": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": 8, + "glyph": "■", + "bulletStyle": {} + } + } + }, + { + "startIndex": 8, + "endIndex": 9, + "textRun": { + "content": "\n", + "style": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + ], + "lists": { + "bodyPlaceholderListEntity": { + "listId": "bodyPlaceholderListEntity", + "nestingLevel": { + "0": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "1": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "2": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "3": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "4": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "5": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "6": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "7": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + }, + "8": { + "bulletStyle": { + "backgroundColor": {}, + "foregroundColor": { + "opaqueColor": { + "rgbColor": {} + } + }, + "bold": false, + "italic": false, + "fontFamily": "Arial", + "fontSize": { + "magnitude": 11, + "unit": "PT" + }, + "baselineOffset": "NONE", + "smallCaps": false, + "strikethrough": false, + "underline": false, + "weightedFontFamily": { + "fontFamily": "Arial", + "weight": 400 + } + } + } + } + } + } + }, + "shapeProperties": { + "shapeBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "outline": { + "outlineFill": { + "solidFill": { + "color": { + "rgbColor": {} + }, + "alpha": 1 + } + }, + "weight": { + "magnitude": 9525, + "unit": "EMU" + }, + "dashStyle": "SOLID", + "propertyState": "NOT_RENDERED" + }, + "shadow": { + "type": "OUTER", + "transform": { + "scaleX": 1, + "scaleY": 1, + "unit": "EMU" + }, + "alignment": "BOTTOM_LEFT", + "blurRadius": { + "unit": "EMU" + }, + "color": { + "rgbColor": {} + }, + "alpha": 1, + "rotateWithShape": false, + "propertyState": "NOT_RENDERED" + }, + "contentAlignment": "TOP" + }, + "placeholder": { + "type": "BODY", + "index": 1 + } + } + } + ], + "pageProperties": { + "pageBackgroundFill": { + "propertyState": "NOT_RENDERED", + "solidFill": { + "color": { + "rgbColor": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + "alpha": 1 + } + }, + "colorScheme": { + "colors": [ + { + "type": "DARK1", + "color": {} + }, + { + "type": "LIGHT1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "DARK2", + "color": { + "red": 0.08235294, + "green": 0.5058824, + "blue": 0.34509805 + } + }, + { + "type": "LIGHT2", + "color": { + "red": 0.9529412, + "green": 0.9529412, + "blue": 0.9529412 + } + }, + { + "type": "ACCENT1", + "color": { + "red": 0.019607844, + "green": 0.5529412, + "blue": 0.78039217 + } + }, + { + "type": "ACCENT2", + "color": { + "red": 0.3137255, + "green": 0.7058824, + "blue": 0.19607843 + } + }, + { + "type": "ACCENT3", + "color": { + "red": 0.92941177, + "green": 0.3372549, + "blue": 0.105882354 + } + }, + { + "type": "ACCENT4", + "color": { + "red": 0.92941177, + "green": 0.9372549 + } + }, + { + "type": "ACCENT5", + "color": { + "red": 0.14117648, + "green": 0.79607844, + "blue": 0.8980392 + } + }, + { + "type": "ACCENT6", + "color": { + "red": 0.39215687, + "green": 0.8980392, + "blue": 0.44705883 + } + }, + { + "type": "HYPERLINK", + "color": { + "red": 0.13333334, + "blue": 0.8 + } + }, + { + "type": "FOLLOWED_HYPERLINK", + "color": { + "red": 0.33333334, + "green": 0.101960786, + "blue": 0.54509807 + } + }, + { + "type": "TEXT1", + "color": {} + }, + { + "type": "BACKGROUND1", + "color": { + "red": 1, + "green": 1, + "blue": 1 + } + }, + { + "type": "TEXT2", + "color": { + "red": 0.9529412, + "green": 0.9529412, + "blue": 0.9529412 + } + }, + { + "type": "BACKGROUND2", + "color": { + "red": 0.08235294, + "green": 0.5058824, + "blue": 0.34509805 + } + } + ] + } + } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From da192a9f580174e3b452079026e70d5c08d8a90f Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 23 Aug 2019 16:11:23 -0400 Subject: added start of an overlay for documents. --- src/client/views/MainView.tsx | 2 +- src/client/views/OverlayView.tsx | 31 ++++++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 1 + .../authentication/models/current_user_utils.ts | 6 +++++ 4 files changed, 37 insertions(+), 3 deletions(-) (limited to 'src/server') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f28844009..a02214deb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -404,7 +404,7 @@ export class MainView extends React.Component { get mainContent() { let sidebar = CurrentUserUtils.UserDocument.sidebar; if (!(sidebar instanceof Doc)) return (null); - return
    + return
    diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 2f2579057..a60dc591c 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,9 +1,15 @@ import * as React from "react"; import { observer } from "mobx-react"; import { observable, action } from "mobx"; -import { Utils } from "../../Utils"; +import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString } from "../../Utils"; import './OverlayView.scss'; +import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { DocListCast, Doc } from "../../new_fields/Doc"; +import { Id } from "../../new_fields/FieldSymbols"; +import { DocumentView } from "./nodes/DocumentView"; +import { Transform } from "../util/Transform"; +import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView"; export type OverlayDisposer = () => void; @@ -134,8 +140,29 @@ export class OverlayView extends React.Component { render() { return ( -
    +
    {this._elements} + {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => ( + ))}
    ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6c944c6b2..3cf86b6f9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -628,6 +628,7 @@ export class DocumentView extends DocComponent(Docu !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); + cm.addItem({ description: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ description: "Download document", icon: "download", event: () => { const a = document.createElement("a"); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index f7ce24967..83e45d3ce 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -73,6 +73,12 @@ export class CurrentUserUtils { sidebar.boxShadow = "1 1 3"; doc.sidebar = sidebar; } + if (doc.overlays === undefined) { + const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" }); + overlays.excludeFromLibrary = true; + Doc.GetProto(overlays).backgroundColor = "#aca3a6"; + doc.overlays = overlays; + } StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); doc.width = 100; doc.preventTreeViewOpen = true; -- cgit v1.2.3-70-g09d2 From 32f066ebc70684fe410b89fcb87cf550bd3537e3 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 27 Aug 2019 14:16:26 -0400 Subject: pdf + new npm version fixes --- src/client/views/pdf/PDFViewer.tsx | 6 ++++++ src/client/views/pdf/Page.tsx | 7 ++++--- src/server/authentication/config/passport.ts | 10 ++++++---- src/server/index.ts | 8 ++++---- 4 files changed, 20 insertions(+), 11 deletions(-) (limited to 'src/server') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 258e218f0..e3bfea237 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -152,12 +152,18 @@ export class PDFViewer extends React.Component { if (this._pageSizes.length === 0) { this._isPage = Array(this.props.pdf.numPages); this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); + this._visibleElements = Array(this.props.pdf.numPages); await Promise.all(this._pageSizes.map>((val, i) => this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { this._pageSizes.splice(i, 1, { width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale, height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale }); + this._visibleElements.splice(i, 1, +
    + "PAGE IS LOADING... " +
    ) this.getPlaceholderPage(i); })))); this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages); diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 0de1777e6..8df2dce29 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -64,13 +64,14 @@ export default class Page extends React.Component { // lower scale = easier to read at small sizes, higher scale = easier to read at large sizes if (this._state !== "rendering" && !this._page && this._canvas.current && this._textLayer.current) { this._state = "rendering"; - let viewport = page.getViewport(scale); + let viewport = page.getViewport({ scale: scale }); this._canvas.current.width = this._width = viewport.width; this._canvas.current.height = this._height = viewport.height; this.props.pageLoaded(viewport); let ctx = this._canvas.current.getContext("2d"); if (ctx) { - page.render({ canvasContext: ctx, viewport: viewport }); // renders the page onto the canvas context + //@ts-ignore + page.render({ canvasContext: ctx, viewport: viewport, enableWebGL: true }); // renders the page onto the canvas context page.getTextContent().then(res => // renders text onto the text container //@ts-ignore Pdfjs.renderTextLayer({ @@ -258,7 +259,7 @@ export default class Page extends React.Component { } } } - let text = selRange.extractContents().textContent; + let text = selRange.cloneContents().textContent; text && this.props.setSelectionText(text); // clear selection diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts index d42741410..10b17de71 100644 --- a/src/server/authentication/config/passport.ts +++ b/src/server/authentication/config/passport.ts @@ -42,9 +42,11 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) = export let isAuthorized = (req: Request, res: Response, next: NextFunction) => { const provider = req.path.split("/").slice(-1)[0]; - if (_.find(req.user.tokens, { kind: provider })) { - next(); - } else { - res.redirect(`/auth/${provider}`); + if (req.user) { + if (_.find((req.user as any).tokens, { kind: provider })) { + next(); + } else { + res.redirect(`/auth/${provider}`); + } } }; \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 34a0a19f1..fca90a585 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -116,7 +116,7 @@ function addSecureRoute(method: Method, ) { let abstracted = (req: express.Request, res: express.Response) => { if (req.user) { - handler(req.user, res, req); + handler(req.user as any, res, req); } else { req.session!.target = req.originalUrl; onRejection(res, req); @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport(1); + let viewport = page.getViewport({ scale: 1 }); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector = req.params.sector; - let action = req.params.action; + let sector: any = req.params.sector; + let action: any = req.params.action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From f6c15087d30f27c15b3b6304af58c93c65536131 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 29 Aug 2019 14:50:22 -0400 Subject: changed linkFollowBox creation approach. fixed target contexts to add link default. --- src/client/views/MainView.tsx | 8 +- src/client/views/OverlayView.tsx | 108 +++++++++++---------- src/client/views/linking/LinkFollowBox.tsx | 20 ++-- src/client/views/linking/LinkMenuItem.tsx | 18 ++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 - src/client/views/nodes/FormattedTextBoxComment.tsx | 8 +- .../authentication/models/current_user_utils.ts | 3 + 7 files changed, 78 insertions(+), 88 deletions(-) (limited to 'src/server') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 1fc8d1397..b64986084 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -438,13 +438,7 @@ export class MainView extends React.Component { if (LinkFollowBox.Instance) { let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document); // if it already exisits, close it - if (dvs.length > 0 && shouldClose) LinkFollowBox.Instance.close(); - // open it if not - else Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", LinkFollowBox.Instance.props.Document); - } - else { - let doc = Docs.Create.LinkFollowBoxDocument({ x: this.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); + LinkFollowBox.Instance.props.Document.isMinimized = (dvs.length > 0 && shouldClose); } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index a1afe651c..54ab8696e 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { observer } from "mobx-react"; -import { observable, action } from "mobx"; +import { observable, action, trace, computed } from "mobx"; import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse } from "../../Utils"; import './OverlayView.scss'; @@ -140,61 +140,65 @@ export class OverlayView extends React.Component { return remove; } + @computed get overlayDocs() { + return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => { + let offsetx = 0, offsety = 0; + let onPointerMove = action((e: PointerEvent) => { + if (e.buttons === 1) { + d.x = e.clientX + offsetx; + d.y = e.clientY + offsety; + e.stopPropagation(); + e.preventDefault(); + } + }); + let onPointerUp = action((e: PointerEvent) => { + document.removeEventListener("pointermove", onPointerMove); + document.removeEventListener("pointerup", onPointerUp); + e.stopPropagation(); + e.preventDefault(); + }); + + let onPointerDown = (e: React.PointerEvent) => { + offsetx = NumCast(d.x) - e.clientX; + offsety = NumCast(d.y) - e.clientY; + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", onPointerMove); + document.addEventListener("pointerup", onPointerUp); + }; + return
    + +
    ; + }) + } + render() { return (
    {this._elements} - {CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => { - let offsetx = 0, offsety = 0; - let onPointerMove = action((e: PointerEvent) => { - if (e.buttons === 1) { - d.x = e.clientX + offsetx; - d.y = e.clientY + offsety; - e.stopPropagation(); - e.preventDefault(); - } - }); - let onPointerUp = action((e: PointerEvent) => { - document.removeEventListener("pointermove", onPointerMove); - document.removeEventListener("pointerup", onPointerUp); - e.stopPropagation(); - e.preventDefault(); - }); - - let onPointerDown = (e: React.PointerEvent) => { - offsetx = NumCast(d.x) - e.clientX; - offsety = NumCast(d.y) - e.clientY; - e.stopPropagation(); - e.preventDefault(); - document.addEventListener("pointermove", onPointerMove); - document.addEventListener("pointerup", onPointerUp); - } - return
    - -
    - })} + {this.overlayDocs}
    ); } diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 07f07f72a..74663f9af 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -1,4 +1,4 @@ -import { observable, computed, action, trace, ObservableMap, runInAction, reaction, IReactionDisposer } from "mobx"; +import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx"; import React = require("react"); import { observer } from "mobx-react"; import { FieldViewProps, FieldView } from "../nodes/FieldView"; @@ -15,8 +15,6 @@ import { SearchUtil } from "../../util/SearchUtil"; import { Id } from "../../../new_fields/FieldSymbols"; import { listSpec } from "../../../new_fields/Schema"; import { DocServer } from "../../DocServer"; -import { RefField } from "../../../new_fields/RefField"; -import { Docs } from "../../documents/Documents"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; @@ -126,9 +124,11 @@ export class LinkFollowBox extends React.Component { const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); docs.forEach(doc => map.delete(doc)); - runInAction(() => { + runInAction(async () => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); + let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc; + runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest })); }); } } @@ -268,7 +268,7 @@ export class LinkFollowBox extends React.Component { let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc); // THIS IS EMPTY FUNCTION this.props.addDocTab(fullScreenAlias, undefined, "inTab"); - console.log(this.props.addDocTab) + console.log(this.props.addDocTab); this.highlightDoc(); SelectionManager.DeselectAll(); @@ -348,7 +348,7 @@ export class LinkFollowBox extends React.Component { } else if (this.selectedMode === FollowModes.OPENTAB) { if (notOpenInContext) this.openLinkTab(); - else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom }) + else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom }); } else if (this.selectedMode === FollowModes.PAN) { this.jumpToLink({ shouldZoom: shouldZoom }); @@ -555,19 +555,13 @@ export class LinkFollowBox extends React.Component { return null; } - async close() { - let res = await DocListCastAsync((CurrentUserUtils.UserDocument.overlays as Doc).data); - if (res) res.splice(res.indexOf(LinkFollowBox.Instance!.props.Document), 1); - LinkFollowBox.Instance = undefined; - } - render() { return (
    {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"} -
    +
    this.props.Document.isMinimized = true} className="closeDocument">
    {LinkFollowBox.linkDoc ? LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title) diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6c10d4fb6..780f161d3 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -97,24 +97,20 @@ export class LinkMenuItem extends React.Component { @action.bound async followDefault() { - if (LinkFollowBox.Instance === undefined) { - let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); - } else { - LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); - LinkFollowBox.Instance!.defaultLinkBehavior(); + if (LinkFollowBox.Instance !== undefined) { + LinkFollowBox.Instance.props.Document.isMinimized = false; + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkFollowBox.Instance.defaultLinkBehavior(); } } @action.bound async openLinkFollower() { - if (LinkFollowBox.Instance === undefined) { - let doc = await Docs.Create.LinkFollowBoxDocument({ x: MainView.Instance.flyoutWidth, y: 20, width: 500, height: 370, title: "Link Follower" }); - await Doc.AddDocToList(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc, "data", doc); - } else { + if (LinkFollowBox.Instance !== undefined) { + LinkFollowBox.Instance.props.Document.isMinimized = false; MainView.Instance.toggleLinkFollowBox(false); + LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); } - LinkFollowBox.Instance!.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); } render() { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7631ecc6c..c9c394960 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -8,7 +8,6 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView" import "./DocumentView.scss"; import React = require("react"); import { Doc } from "../../../new_fields/Doc"; -import { returnEmptyString } from "../../../Utils"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { x?: number; diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 9123f8aed..0287d93a9 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,12 +1,12 @@ -import { Plugin, EditorState } from "prosemirror-state" -import './FormattedTextBoxComment.scss' +import { Plugin, EditorState } from "prosemirror-state"; +import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../new_fields/Doc"; export let selectionSizePlugin = new Plugin({ view(editorView) { return new SelectionSizeTooltip(editorView); } -}) +}); export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } @@ -106,5 +106,5 @@ export class SelectionSizeTooltip { } } - destroy() { SelectionSizeTooltip.tooltip.style.display = "none" } + destroy() { SelectionSizeTooltip.tooltip.style.display = "none"; } } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 83e45d3ce..9866e22eb 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -79,6 +79,9 @@ export class CurrentUserUtils { Doc.GetProto(overlays).backgroundColor = "#aca3a6"; doc.overlays = overlays; } + if (doc.linkFollowBox === undefined) { + PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" }))); + } StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library"); doc.width = 100; doc.preventTreeViewOpen = true; -- cgit v1.2.3-70-g09d2 From 4921ef0121818381e4139137c0d204d41cb64a20 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 3 Sep 2019 18:57:23 -0400 Subject: reverted back to semi-working version of outline mode --- src/client/views/nodes/FormattedTextBox.tsx | 9 +++++---- src/server/index.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/server') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b46502c1d..4cb52ec78 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -813,10 +813,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); if (e.key === "Tab" || e.key === "Enter") { // bullets typically change "levels" when tab or enter is used. sometimes backspcae, so maybe that should be added. e.preventDefault(); e.preventDefault(); - setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) - SelectionManager.DeselectAll(); - SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); - }, 0); + // bcz: if we use this, it fixes some problesm with bullet numbers but kills undo/redo + // setTimeout(() => { // force re-rendering of bullet numbers that may have had their bullet labels change. (Our prosemirrior code re-"marks" the changed bullets, but nothing causes the Dom to be re-rendered which is where the nubering takes place) + // SelectionManager.DeselectAll(); + // SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(this.props.Document, this.props.ContainingCollectionView)!, false); + // }, 0); } function timenow() { var now = new Date(); diff --git a/src/server/index.ts b/src/server/index.ts index fca90a585..17cd59ec7 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport({ scale: 1 }); + let viewport = page.getViewport(1); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From 3e94ae1fff76cf7190d3f95ff2b47e781c315af6 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Fri, 6 Sep 2019 16:20:53 -0400 Subject: index fix --- src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server') diff --git a/src/server/index.ts b/src/server/index.ts index 17cd59ec7..d2c6584cc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport(1); + let viewport = page.getViewport({scale: 1}); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, -- cgit v1.2.3-70-g09d2 From f43294e192279fd3205be54f891a97ea3ccb6e99 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 9 Sep 2019 11:26:09 -0400 Subject: server compiling tweaks --- src/server/apis/google/GooglePhotosUploadUtils.ts | 176 ++++++++++++++++++++++ src/server/index.ts | 6 +- 2 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 src/server/apis/google/GooglePhotosUploadUtils.ts (limited to 'src/server') diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts new file mode 100644 index 000000000..35f986250 --- /dev/null +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -0,0 +1,176 @@ +import request = require('request-promise'); +import { GoogleApiServerUtils } from './GoogleApiServerUtils'; +import * as fs from 'fs'; +import { Utils } from '../../../Utils'; +import * as path from 'path'; +import { Opt } from '../../../new_fields/Doc'; +import * as sharp from 'sharp'; + +const uploadDirectory = path.join(__dirname, "../../public/files/"); + +export namespace GooglePhotosUploadUtils { + + export interface Paths { + uploadDirectory: string; + credentialsPath: string; + tokenPath: string; + } + + export interface MediaInput { + url: string; + description: string; + } + + const prepend = (extension: string) => `https://photoslibrary.googleapis.com/v1/${extension}`; + const headers = (type: string) => ({ + 'Content-Type': `application/${type}`, + 'Authorization': Bearer, + }); + + let Bearer: string; + let Paths: Paths; + + export const initialize = async (paths: Paths) => { + Paths = paths; + const { tokenPath, credentialsPath } = paths; + const token = await GoogleApiServerUtils.RetrieveAccessToken({ tokenPath, credentialsPath }); + Bearer = `Bearer ${token}`; + }; + + export const DispatchGooglePhotosUpload = async (url: string) => { + const body = await request(url, { encoding: null }); + const parameters = { + method: 'POST', + headers: { + ...headers('octet-stream'), + 'X-Goog-Upload-File-Name': path.basename(url), + 'X-Goog-Upload-Protocol': 'raw' + }, + uri: prepend('uploads'), + body + }; + return new Promise(resolve => request(parameters, (error, _response, body) => resolve(error ? undefined : body))); + }; + + export const CreateMediaItems = (newMediaItems: any[], album?: { id: string }) => { + return new Promise((resolve, reject) => { + const parameters = { + method: 'POST', + headers: headers('json'), + uri: prepend('mediaItems:batchCreate'), + body: { newMediaItems } as any, + json: true + }; + album && (parameters.body.albumId = album.id); + request(parameters, (error, _response, body) => { + if (error) { + reject(error); + } else { + resolve(body); + } + }); + }); + }; + +} + +export namespace DownloadUtils { + + export interface Size { + width: number; + suffix: string; + } + + export const Sizes: { [size: string]: Size } = { + SMALL: { width: 100, suffix: "_s" }, + MEDIUM: { width: 400, suffix: "_m" }, + LARGE: { width: 900, suffix: "_l" }, + }; + + const png = ".png"; + const pngs = [".png", ".PNG"]; + const jpgs = [".jpg", ".JPG", ".jpeg", ".JPEG"]; + const formats = [".jpg", ".png", ".gif"]; + const size = "content-length"; + const type = "content-type"; + + export interface UploadInformation { + mediaPaths: string[]; + fileNames: { [key: string]: string }; + contentSize?: number; + contentType?: string; + } + + const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`; + const sanitize = (filename: string) => filename.replace(/\s+/g, "_"); + + export const UploadImage = async (url: string, filename?: string, prefix = ""): Promise> => { + const resolved = filename ? sanitize(filename) : generate(prefix, url); + const extension = path.extname(url) || path.extname(resolved) || png; + let information: UploadInformation = { + mediaPaths: [], + fileNames: { clean: resolved } + }; + const { isLocal, stream, normalized } = classify(url); + url = normalized; + if (!isLocal) { + const metadata = (await new Promise((resolve, reject) => { + request.head(url, async (error, res) => { + if (error) { + return reject(error); + } + resolve(res); + }); + })).headers; + information.contentSize = parseInt(metadata[size]); + information.contentType = metadata[type]; + } + return new Promise(async (resolve, reject) => { + const resizers = [ + { resizer: sharp().rotate(), suffix: "_o" }, + ...Object.values(Sizes).map(size => ({ + resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(), + suffix: size.suffix + })) + ]; + if (pngs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.png()); + } else if (jpgs.includes(extension)) { + resizers.forEach(element => element.resizer = element.resizer.jpeg()); + } else if (!formats.includes(extension.toLowerCase())) { + return reject(); + } + for (let resizer of resizers) { + const suffix = resizer.suffix; + let mediaPath: string; + await new Promise(resolve => { + const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension; + information.mediaPaths.push(mediaPath = uploadDirectory + filename); + information.fileNames[suffix] = filename; + stream(url).pipe(resizer.resizer).pipe(fs.createWriteStream(mediaPath)) + .on('close', resolve) + .on('error', reject); + }); + } + resolve(information); + }); + }; + + const classify = (url: string) => { + const isLocal = /Dash-Web(\\|\/)src(\\|\/)server(\\|\/)public(\\|\/)files/g.test(url); + return { + isLocal, + stream: isLocal ? fs.createReadStream : request, + normalized: isLocal ? path.normalize(url) : url + }; + }; + + export const createIfNotExists = async (path: string) => { + if (await new Promise(resolve => fs.exists(path, resolve))) { + return true; + } + return new Promise(resolve => fs.mkdir(path, error => resolve(error === null))); + }; + + export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +} \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index d2c6584cc..082e9422d 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -447,7 +447,7 @@ function LoadPage(file: string, pageNumber: number, res: Response) { console.log(pageNumber); pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { console.log("reading " + page); - let viewport = page.getViewport({scale: 1}); + let viewport = page.getViewport(1 as any); let canvasAndContext = factory.create(viewport.width, viewport.height); let renderContext = { canvasContext: canvasAndContext.context, @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector: any = req.params.sector; - let action: any = req.params.action; + let sector: GoogleApiServerUtils.Service = req.params.sector; + let action: GoogleApiServerUtils.Action = req.params.action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From a8be7ef95403a1f4559aeff9695e67ad2030e3b9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 9 Sep 2019 21:34:34 -0400 Subject: restored input parsing rules back to prosemirror and added some news --- src/client/util/RichTextRules.ts | 52 ++++++++++++++++++----------- src/client/util/prosemirrorPatches.js | 47 ++++++++++++++++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 +- src/server/index.ts | 4 +-- 4 files changed, 83 insertions(+), 23 deletions(-) (limited to 'src/server') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 8c4c76027..979b76988 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -1,14 +1,6 @@ -import { - inputRules, - wrappingInputRule, - textblockTypeInputRule, - smartQuotes, - emDash, - ellipsis -} from "prosemirror-inputrules"; -import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"; - +import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules"; import { schema } from "./RichTextSchema"; +import { wrappingInputRule } from "./prosemirrorPatches"; export const inpRules = { rules: [ @@ -21,17 +13,29 @@ export const inpRules = { // 1. ordered list wrappingInputRule( - /^(\d+)\.\s$/, + /^1\.\s$/, schema.nodes.ordered_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + () => { + return ({ mapStyle: "decimal", bulletStyle: 1 }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } }) ), // a. alphabbetical list wrappingInputRule( - /^([a-z]+)\.\s$/, - schema.nodes.alphabet_list, - match => ({ order: +match[1] }), - (match, node) => node.childCount + node.attrs.order === +match[1] + /^a\.\s$/, + schema.nodes.ordered_list, + // match => { + () => { + return ({ mapStyle: "alpha", bulletStyle: 1 }) + // return ({ order: +match[1] }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "alpha", bulletStyle: 1 } }) ), // * bullet list @@ -42,9 +46,17 @@ export const inpRules = { // # heading textblockTypeInputRule( - new RegExp("^(#{1,6})\\s$"), + new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, - match => ({ level: match[1].length }) - ) + match => { + return ({ level: match[1].length }); + } + ), + + new InputRule( + new RegExp(/^#([0-9]+)\s$/), + (state, match, start, end) => { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) + }), ] }; diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js index 6bf4395ad..188e3e1c5 100644 --- a/src/client/util/prosemirrorPatches.js +++ b/src/client/util/prosemirrorPatches.js @@ -2,11 +2,13 @@ Object.defineProperty(exports, '__esModule', { value: true }); +var prosemirrorInputRules = require('prosemirror-inputrules'); var prosemirrorTransform = require('prosemirror-transform'); var prosemirrorModel = require('prosemirror-model'); exports.liftListItem = liftListItem; exports.sinkListItem = sinkListItem; +exports.wrappingInputRule = wrappingInputRule; // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool // Create a command to lift the list item around the selection up into // a wrapping list. @@ -89,4 +91,49 @@ function sinkListItem(itemType) { } return true } +} + +function findWrappingOutside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var around = parent.contentMatchAt(startIndex).findWrapping(type); + if (!around) { return null } + var outer = around.length ? around[0] : type; + return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null +} + +function findWrappingInside(range, type) { + var parent = range.parent; + var startIndex = range.startIndex; + var endIndex = range.endIndex; + var inner = parent.child(startIndex); + var inside = type.contentMatch.findWrapping(inner.type); + if (!inside) { return null } + var lastType = inside.length ? inside[inside.length - 1] : type; + var innerMatch = lastType.contentMatch; + for (var i = startIndex; innerMatch && i < endIndex; i++) { innerMatch = innerMatch.matchType(parent.child(i).type); } + if (!innerMatch || !innerMatch.validEnd) { return null } + return inside +} +function findWrapping(range, nodeType, attrs, innerRange, customWithAttrs = null) { + if (innerRange === void 0) innerRange = range; + let withAttrs = (type) => ({ type: type, attrs: null }); + var around = findWrappingOutside(range, nodeType); + var inner = around && findWrappingInside(innerRange, nodeType); + if (!inner) { return null } + return around.map(withAttrs).concat({ type: nodeType, attrs: attrs }).concat(inner.map(customWithAttrs ? customWithAttrs : withAttrs)) +} +function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWithAttrs = null) { + return new prosemirrorInputRules.InputRule(regexp, function (state, match, start, end) { + var attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; + var tr = state.tr.delete(start, end); + var $start = tr.doc.resolve(start), range = $start.blockRange(), wrapping = range && findWrapping(range, nodeType, attrs, undefined, customWithAttrs); + if (!wrapping) { return null } + tr.wrap(range, wrapping); + var before = tr.doc.resolve(start - 1).nodeBefore; + if (before && before.type == nodeType && prosemirrorTransform.canJoin(tr.doc, start - 1) && + (!joinPredicate || joinPredicate(match, before))) { tr.join(start - 1); } + return tr + }) } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 93c97fa23..6a6000dc5 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -38,6 +38,7 @@ import { DictationManager } from '../../util/DictationManager'; import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; +import { inputRules } from 'prosemirror-inputrules'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -355,8 +356,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, - inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ + inputRules(inpRules), this.tooltipTextMenuPlugin(), history(), keymap(this._keymap), diff --git a/src/server/index.ts b/src/server/index.ts index 082e9422d..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -811,8 +811,8 @@ const EndpointHandlerMap = new Map { - let sector: GoogleApiServerUtils.Service = req.params.sector; - let action: GoogleApiServerUtils.Action = req.params.action; + let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service; + let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action; GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], { credentials, token }).then(endpoint => { let handler = EndpointHandlerMap.get(action); if (endpoint && handler) { -- cgit v1.2.3-70-g09d2 From 5ea7e3318620865146318e0f3826b6f13aec0675 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 10 Sep 2019 13:38:16 -0400 Subject: added more support for creating stylized layouts --- src/Utils.ts | 92 ++++++++++++++++++++++ src/client/util/ProsemirrorExampleTransfer.ts | 5 -- src/client/util/RichTextRules.ts | 40 ++++++++++ src/client/util/RichTextSchema.tsx | 19 +++++ src/client/views/ContextMenu.tsx | 1 + src/client/views/DocumentDecorations.tsx | 5 ++ src/client/views/InkingControl.tsx | 32 +++++++- .../collectionFreeForm/CollectionFreeFormView.tsx | 27 ++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++++- src/client/views/nodes/FormattedTextBox.tsx | 48 ++++++++--- .../authentication/models/current_user_utils.ts | 20 ++--- 11 files changed, 270 insertions(+), 45 deletions(-) (limited to 'src/server') diff --git a/src/Utils.ts b/src/Utils.ts index f805ae872..3921a49c3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -53,6 +53,98 @@ export class Utils { document.body.removeChild(textArea); } + public static fromRGBAstr(rgba: string) { + let rm = rgba.match(/rgb[a]?\(([0-9]+)/); + let r = rm ? Number(rm[1]) : 0; + let gm = rgba.match(/rgb[a]?\([0-9]+,([0-9]+)/); + let g = gm ? Number(gm[1]) : 0; + let bm = rgba.match(/rgb[a]?\([0-9]+,[0-9]+,([0-9]+)/); + let b = bm ? Number(bm[1]) : 0; + let am = rgba.match(/rgba?\([0-9]+,[0-9]+,[0-9]+,([0-9]+)/); + let a = am ? Number(am[1]) : 0; + return { r: r, g: g, b: b, a: a }; + } + public static toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { + return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; + } + + public static HSLtoRGB(h: number, s: number, l: number) { + // Must be fractions of 1 + // s /= 100; + // l /= 100; + + let c = (1 - Math.abs(2 * l - 1)) * s, + x = c * (1 - Math.abs((h / 60) % 2 - 1)), + m = l - c / 2, + r = 0, + g = 0, + b = 0; + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + return { r: r, g: g, b: b }; + } + + public static RGBToHSL(r: number, g: number, b: number) { + // Make r, g, and b fractions of 1 + r /= 255; + g /= 255; + b /= 255; + + // Find greatest and smallest channel values + let cmin = Math.min(r, g, b), + cmax = Math.max(r, g, b), + delta = cmax - cmin, + h = 0, + s = 0, + l = 0; + // Calculate hue + + // No difference + if (delta == 0) + h = 0; + // Red is max + else if (cmax == r) + h = ((g - b) / delta) % 6; + // Green is max + else if (cmax == g) + h = (b - r) / delta + 2; + // Blue is max + else + h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + // Make negative hues positive behind 360° + if (h < 0) + h += 360; // Calculate lightness + + l = (cmax + cmin) / 2; + + // Calculate saturation + s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + + // Multiply l and s by 100 + // s = +(s * 100).toFixed(1); + // l = +(l * 100).toFixed(1); + + return { h: h, s: s, l: l }; + } + + public static GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index bac0177ad..1d2d33800 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -14,7 +14,6 @@ export type KeyMap = { [key: string]: any }; export default function buildKeymap>(schema: S, mapKeys?: KeyMap): KeyMap { let keys: { [key: string]: any } = {}, type; - keys["ACTIVE"] = false; function bind(key: string, cmd: any) { if (mapKeys) { let mapped = mapKeys[key]; @@ -148,10 +147,6 @@ export default function buildKeymap>(schema: S, mapKeys?: return tx; } bind("Enter", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!keys["ACTIVE"]) {// hack to ignore an initial carriage return when creating a textbox from the action menu - dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from - 1, state.selection.from)).deleteSelection()); - return true; - } var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => dispatch(tx3))) { if (!splitBlockKeepMarks(state, (tx3: Transaction) => { diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 5d1131410..00e671db9 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -2,6 +2,9 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; import { NodeSelection } from "prosemirror-state"; +import { NumCast, Cast } from "../../new_fields/Types"; +import { Doc } from "../../new_fields/Doc"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; export const inpRules = { rules: [ @@ -57,6 +60,12 @@ export const inpRules = { new InputRule( new RegExp(/^#([0-9]+)\s$/), (state, match, start, end) => { + let size = Number(match[1]); + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })) }), new InputRule( @@ -64,9 +73,40 @@ export const inpRules = { (state, match, start, end) => { let node = (state.doc.resolve(start) as any).nodeAfter; let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "center"; + } return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), + new InputRule( + new RegExp(/^\[\[\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "left"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), + new InputRule( + new RegExp(/^\]\]\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc; + let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleAlign_" + heading] = "right"; + } + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + }), new InputRule( new RegExp(/\^f\s$/), (state, match, start, end) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3def4a579..f027a4bf7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -12,6 +12,7 @@ import { DocServer } from "../DocServer"; import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; +import { times } from "async"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -422,6 +423,24 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, + // pFontFamily: { + // attrs: { + // style: { default: 'font-family: "Times New Roman", Times, serif;' }, + // }, + // parseDOM: [{ + // tag: "span", getAttrs(dom: any) { + // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; + // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; + // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; + // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; + // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; + // } + // }], + // toDOM: (node: any) => ['span', { + // style: node.attrs.style + // }] + // }, + /* FONTS */ timesNewRoman: { diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 890bfdfb7..68b97f2b6 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -250,6 +250,7 @@ export class ContextMenu extends React.Component { const item = this.flatItems[this.selectedIndex]; item && item.event(); this.closeMenu(); + e.preventDefault(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7cdb16f52..94aab8b2f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -621,6 +621,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> doc.y = (doc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight); + if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) { + doc.ignoreAspect = false; + proto.nativeWidth = nwidth = doc.width || 0; + proto.nativeHeight = nheight = doc.height || 0; + } if (fixedAspect && (!nwidth || !nheight)) { proto.nativeWidth = nwidth = doc.width || 0; proto.nativeHeight = nheight = doc.height || 0; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index eb6312e78..519792308 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,8 +10,10 @@ import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { StrCast, NumCast, Cast } from "../../new_fields/Types"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { MainOverlayTextBox } from "./MainOverlayTextBox"; +import { listSpec } from "../../new_fields/Schema"; +import { List } from "../../new_fields/List"; +import { Utils } from "../../Utils"; library.add(faPen, faHighlighter, faEraser, faBan); @@ -49,11 +51,34 @@ export class InkingControl extends React.Component { let oldColors = selected.map(view => { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let oldColor = StrCast(targetDoc.backgroundColor); - targetDoc.backgroundColor = this._selectedColor; + if (view.props.ContainingCollectionView && view.props.ContainingCollectionView.props.Document.colorPalette) { + let cp = Cast(view.props.ContainingCollectionView.props.Document.colorPalette, listSpec("string")) as string[]; + let closest = 0; + let dist = 10000000; + let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor)); + for (let i = 0; i < cp.length; i++) { + let cpcol = Utils.fromRGBAstr(cp[i]); + let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g)); + if (d < dist) { + dist = d; + closest = i; + } + } + cp[closest] = "rgb(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + ")"; + view.props.ContainingCollectionView.props.Document.colorPalette = new List(cp); + targetDoc.backgroundColor = cp[closest]; + } else + targetDoc.backgroundColor = this._selectedColor; if (view.props.Document.heading) { let cv = view.props.ContainingCollectionView; let ruleProvider = cv && (Cast(cv.props.Document.ruleProvider, Doc) as Doc); - cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = this._selectedColor); + let parback = cv && StrCast(cv.props.Document.backgroundColor); + cv && parback && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)); + // if (parback && cv && parback.indexOf("rgb") !== -1) { + // let parcol = Utils.fromRGBAstr(parback); + // let hsl = Utils.RGBToHSL(parcol.r, parcol.g, parcol.b); + // cv && ((ruleProvider ? ruleProvider : cv.props.Document)["ruleColor_" + NumCast(view.props.Document.heading)] = color.hsl.s - hsl.s); + // } } return { target: targetDoc, @@ -67,7 +92,6 @@ export class InkingControl extends React.Component { }); } }); - @action switchWidth = (width: string): void => { this._selectedWidth = width; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f7c1bedbb..c9b2c30b1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs } from "../../../documents/Documents"; @@ -262,14 +262,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { newBox.heading = 2; } } - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (!(ruleProvider instanceof Doc)) ruleProvider = this.props.Document; - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (newBox.borderRounding = round); - col && (newBox.backgroundColor = col); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); + PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { + if (!ruleProvider) ruleProvider = this.props.Document; + // saturation shift + // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor)); + // let hsl = Utils.RGBToHSL(back.r, back.g, back.b); + // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l }; + // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); + // OR transparency set + let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); + col && (Doc.GetProto(newBox).backgroundColor = col); + + let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); + round && (newBox.borderRounding = round); + newBox.ruleProvider = ruleProvider; + this.addDocument(newBox, false); + }); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 100e6d817..56d8127e2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,11 +1,11 @@ import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult } from "../../../../new_fields/Doc"; +import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; -import { Cast, NumCast } from "../../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; @@ -20,6 +20,8 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; +import { string } from "prop-types"; +import { listSpec } from "../../../../new_fields/Schema"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -272,14 +274,30 @@ export class MarqueeView extends React.Component return d; }); } + let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let usedPaletted = new Map(); + [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + let bg = StrCast(child.backgroundColor); + if (palette.indexOf(bg) !== -1) { + palette.splice(palette.indexOf(bg), 1); + if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); + else usedPaletted.set(bg, 1); + } + }); + let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); + let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, panY: 0, - backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", - defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", + backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 6a6000dc5..0ea36cdc2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; -import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Utils, numberRange, timenow } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -39,6 +39,7 @@ import { ReplaceStep } from 'prosemirror-transform'; import { DocumentType } from '../../documents/DocumentTypes'; import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { inputRules } from 'prosemirror-inputrules'; +import { select } from 'async'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -203,8 +204,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } - this._keymap["ACTIVE"] = true; // hack to ignore an initial carriage return when creating a textbox from the action menu - this._applyingChange = true; this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); @@ -353,7 +352,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); - this._keymap["ACTIVE"] = this.extensionDoc.text; // hack to ignore an initial carriage return only when creating a textbox from the action menu return { schema, plugins: this.props.isOverlay ? [ @@ -659,7 +657,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (this.props.Document[Id] === FormattedTextBox.SelectOnLoad) { + let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad; + if (selectOnLoad) { FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } @@ -667,15 +666,38 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; let heading = this.props.Document.heading; - if (heading) { - let ruleProvider = Cast(this.props.Document.ruleProvider, Doc); - if (ruleProvider instanceof Doc) { - let font = StrCast(ruleProvider["ruleFont_" + heading]); - let size = NumCast(ruleProvider["ruleSize_" + heading]); - size && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, schema.marks.pFontSize.create({ fontSize: size })]); - font && (this._editorView!.state.storedMarks = [...this._editorView!.state.storedMarks, font === "Arial" ? schema.marks.arial.create() : schema.marks.comicSans.create()]); - } + if (heading && selectOnLoad) { + PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { + if (ruleProvider) { + let align = StrCast(ruleProvider["ruleAlign_" + heading]); + let font = StrCast(ruleProvider["ruleFont_" + heading]); + let size = NumCast(ruleProvider["ruleSize_" + heading]); + if (align) { + let tr = this._editorView!.state.tr; + tr = tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(2))). + replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: align }), true). + setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(0))); + this._editorView!.dispatch(tr); + } + let sm = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; + size && (sm = [...sm, schema.marks.pFontSize.create({ fontSize: size })]); + font && (sm = [...sm, this.getFont(font)]); + this._editorView!.dispatch(this._editorView!.state.tr.setStoredMarks(sm)); + } + }); + } + } + getFont(font: string) { + switch (font) { + case "Arial": return schema.marks.arial.create(); + case "Times New Roman": return schema.marks.timesNewRoman.create(); + case "Georgia": return schema.marks.georgia.create(); + case "Comic Sans MS": return schema.marks.comicSans.create(); + case "Tahoma": return schema.marks.tahoma.create(); + case "Impact": return schema.marks.impact.create(); + case "ACrimson Textrial": return schema.marks.crimson.create(); } + return schema.marks.arial.create(); } componentWillUnmount() { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9866e22eb..9d35d36d3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -115,17 +115,17 @@ export class CurrentUserUtils { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } }); - try { - const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); - NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); - await Gateway.Instance.ClearCatalog(); - const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); - let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); - let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); - // if (catprom) await Promise.all(catprom); - } catch (e) { + // try { + // const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" }); + // NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json()); + // await Gateway.Instance.ClearCatalog(); + // const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []); + // let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc))); + // let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras); + // // if (catprom) await Promise.all(catprom); + // } catch (e) { - } + // } } /* Northstar catalog ... really just for testing so this should eventually go away */ -- cgit v1.2.3-70-g09d2 From a709e21384cef80a85eac9220739c854a96d5313 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 20:15:40 -0400 Subject: fixed several search issues with text boxes and highlighting. --- .../collections/collectionFreeForm/MarqueeView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 16 ++++--- src/client/views/search/SearchBox.tsx | 6 +-- src/client/views/search/SearchItem.tsx | 56 ++-------------------- src/server/Search.ts | 3 ++ src/server/index.ts | 3 ++ 6 files changed, 27 insertions(+), 62 deletions(-) (limited to 'src/server') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 56d8127e2..0c4860be1 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -288,8 +288,11 @@ export class MarqueeView extends React.Component else usedPaletted.set(bg, 1); } }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = usedPaletted.get("white") || usedPaletted.get("rgb(255,255,255)") && usedPaletted.size === 1 ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; let inkData = this.ink ? this.ink.inkData : undefined; let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 633455f63..1e755f121 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -644,12 +644,16 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Move To Overlay", icon: "laptop-code", event: () => ((o: Doc) => o && Doc.AddDocToList(o, "data", this.props.Document))(Cast(CurrentUserUtils.UserDocument.overlays, Doc) as Doc) }); cm.addItem({ - description: "Download document", icon: "download", event: () => { - const a = document.createElement("a"); - const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - a.href = url; - a.download = `DocExport-${this.props.Document[Id]}.zip`; - a.click(); + description: "Download document", icon: "download", event: async () => { + let y = JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), { + qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' } + })); + console.log(y); + // const a = document.createElement("a"); + // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + // a.href = url; + // a.download = `DocExport-${this.props.Document[Id]}.zip`; + // a.click(); } }); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2ad69daca..2e29838e6 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -141,7 +141,7 @@ export class SearchBox extends React.Component { private get filterQuery() { const types = FilterBox.Instance.filterTypes; const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); + return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : ""); } @@ -304,14 +304,14 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 386b5fe74..0b722c086 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss"; export interface SearchItemProps { doc: Doc; - query?: string; + query: string; highlighting: string[]; } @@ -128,68 +128,26 @@ export class LinkContextMenu extends React.Component { export class SearchItem extends React.Component { @observable _selected: boolean = false; - private _previewDoc?: Doc; onClick = () => { // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like DocumentManager.Instance.jumpToDocument(this.props.doc, false); - if (this.props.doc.data instanceof RichTextField) { - this.highlightTextBox(this.props.doc); - } - // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined); } @observable _useIcons = true; @observable _displayDim = 50; - highlightTextBox = (doc: Doc) => { - if (this.props.query) { - const fieldkey = 'search_string'; - if (Object.keys(doc).indexOf(fieldkey) === -1) { - doc.search_string = this.props.query; - } - else { - doc.search_string = undefined; - } - - } - } - - fitToBox = () => { - let bounds = Doc.ComputeContentBounds([this.props.doc]); - return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim]; - } - componentWillUnmount() { - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } + this.props.doc.search_string = undefined; } - //@computed @action public DocumentIcon() { let layoutresult = StrCast(this.props.doc.type); if (!this._useIcons) { - let renderDoc = this.props.doc; - //let box: number[] = []; - if (layoutresult.indexOf(DocumentType.COL) !== -1) { - renderDoc = Doc.MakeDelegate(renderDoc); - let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { - var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim]; - } let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); let returnYDimension = () => this._displayDim; - let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); - let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt - this._previewDoc = newRenderDoc; + let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension()); const docview =
    { this._useIcons = !this._useIcons; @@ -219,15 +177,9 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
    ; - const data = renderDoc.data; - if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); - newRenderDoc.preview = true; - newRenderDoc.search_string = this.props.query; + this.props.doc.search_string = this.props.query; return docview; } - if (this._previewDoc) { - DocServer.DeleteDocument(this._previewDoc[Id]); - } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote : diff --git a/src/server/Search.ts b/src/server/Search.ts index 723dc101b..4a408405a 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,10 +32,13 @@ export class Search { public async search(query: any) { try { + console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); + console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; + console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 50ce2b14e..149bacf0f 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,12 +198,15 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; + console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } + console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); + console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From 4df85ecd5026127c27b147b34398822307715e54 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Sep 2019 21:36:42 -0400 Subject: fixed search highlighting --- .../views/collections/CollectionTreeView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 13 ++++++-- src/client/views/search/SearchBox.tsx | 38 +++++++++++----------- src/client/views/search/SearchItem.tsx | 12 ++++--- src/server/Search.ts | 3 -- src/server/index.ts | 3 -- 6 files changed, 38 insertions(+), 32 deletions(-) (limited to 'src/server') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 50f03005c..e31fa0b40 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -373,6 +373,7 @@ class TreeView extends React.Component { style={{ color: this.props.document.isMinimized ? "red" : "black", background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0", + fontWeight: this.props.document.search_string ? "bold" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" }} > diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1e755f121..73426b3dc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -780,6 +780,10 @@ export class DocumentView extends DocComponent(Docu let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding); let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree; + let searchHighlight = (!this.props.Document.search_fields ? (null) : +
    + {StrCast(this.props.Document.search_fields)} +
    ); return (
    (Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - {!showTitle && !showCaption ? this.contents : + {!showTitle && !showCaption ? + this.props.Document.search_fields ?
    + {this.contents} + {searchHighlight} +
    : + this.contents :
    -
    {this.contents}
    @@ -828,6 +836,7 @@ export class DocumentView extends DocComponent(Docu
    } + {searchHighlight}
    }
    diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2e29838e6..b30fd83e8 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,25 +1,23 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action, runInAction, flow, computed } from 'mobx'; -import "./SearchBox.scss"; -import "./FilterBox.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core'; -import { SetupDrag } from '../../util/DragManager'; -import { Docs } from '../../documents/Documents'; -import { NumCast, Cast } from '../../../new_fields/Types'; -import { Doc } from '../../../new_fields/Doc'; -import { SearchItem } from './SearchItem'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import * as rp from 'request-promise'; +import { Doc } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; -import { SearchUtil } from '../../util/SearchUtil'; +import { Cast, NumCast } from '../../../new_fields/Types'; import { RouteStore } from '../../../server/RouteStore'; -import { FilterBox } from './FilterBox'; -import { ReadStream } from 'fs'; -import * as $ from 'jquery'; -import { MainView } from '../MainView'; import { Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { SearchUtil } from '../../util/SearchUtil'; +import { MainView } from '../MainView'; +import { FilterBox } from './FilterBox'; +import "./FilterBox.scss"; +import "./SearchBox.scss"; +import { SearchItem } from './SearchItem'; library.add(faTimes); @@ -304,14 +302,16 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } else { result = this._results[i]; if (result) { - this._visibleElements[i] = ; + let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + this._visibleElements[i] = ; this._isSearch[i] = "search"; } } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 0b722c086..30e0454f3 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -136,8 +136,13 @@ export class SearchItem extends React.Component { @observable _useIcons = true; @observable _displayDim = 50; + componentDidMount() { + this.props.doc.search_string = this.props.query; + this.props.doc.search_fields = this.props.highlighting.join(", "); + } componentWillUnmount() { this.props.doc.search_string = undefined; + this.props.doc.search_fields = undefined; } //@computed @@ -177,7 +182,6 @@ export class SearchItem extends React.Component { ContentScaling={scale} />
    ; - this.props.doc.search_string = this.props.query; return docview; } let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : @@ -231,8 +235,7 @@ export class SearchItem extends React.Component { Doc.BrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element => - Doc.BrushDoc(element.props.Document)); + Doc.BrushDoc(this.props.doc); } } @@ -246,8 +249,7 @@ export class SearchItem extends React.Component { Doc.UnBrushDoc(doc2); } } else { - DocumentManager.Instance.getAllDocumentViews(this.props.doc). - forEach(element => Doc.UnBrushDoc(element.props.Document)); + Doc.UnBrushDoc(this.props.doc); } } diff --git a/src/server/Search.ts b/src/server/Search.ts index 4a408405a..723dc101b 100644 --- a/src/server/Search.ts +++ b/src/server/Search.ts @@ -32,13 +32,10 @@ export class Search { public async search(query: any) { try { - console.log("SEARCH " + query + " " + (this.url + "dash/select") + " " + query.q); - console.log(query); const searchResults = JSON.parse(await rp.get(this.url + "dash/select", { qs: query })); const { docs, numFound } = searchResults.response; - console.log("RESULTS " + numFound); const ids = docs.map((field: any) => field.id); return { ids, numFound, highlighting: searchResults.highlighting }; } catch { diff --git a/src/server/index.ts b/src/server/index.ts index 149bacf0f..50ce2b14e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -198,15 +198,12 @@ const solrURL = "http://localhost:8983/solr/#/dash"; app.get("/search", async (req, res) => { const solrQuery: any = {}; - console.log("GOT SEARCH"); ["q", "fq", "start", "rows", "hl", "hl.fl"].forEach(key => solrQuery[key] = req.query[key]); if (solrQuery.q === undefined) { res.send([]); return; } - console.log("CALLING SEARCH") let results = await Search.Instance.search(solrQuery); - console.log("RETURNING SEARCH") res.send(results); }); -- cgit v1.2.3-70-g09d2 From 68e554cafb6107bfde9526773b3e0e667d582c88 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 11 Sep 2019 17:24:48 -0400 Subject: added default note type collection --- src/client/documents/Documents.ts | 1 + src/client/views/Main.tsx | 1 - src/client/views/PreviewCursor.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 16 +++++++++------- .../views/collections/collectionFreeForm/MarqueeView.tsx | 6 ++++++ src/client/views/nodes/FormattedTextBox.tsx | 3 ++- src/server/authentication/models/current_user_utils.ts | 12 +++++++++++- 7 files changed, 30 insertions(+), 11 deletions(-) (limited to 'src/server') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ae65fde1e..602a7f9ad 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -66,6 +66,7 @@ export interface DocumentOptions { page?: number; scale?: number; layout?: string; + isTemplate?: boolean; templates?: List; viewType?: number; backgroundColor?: string; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0e687737d..11ec6f0c9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -37,7 +37,6 @@ let swapDocs = async () => { (await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled"; (await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled"; - CurrentUserUtils.UserDocument.chromeStatus = "disabled"; await swapDocs(); document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 45a8556bf..1aed51e64 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -102,7 +102,7 @@ export class PreviewCursor extends React.Component<{}> { (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys !e.key.startsWith("Arrow") && !e.defaultPrevented) { - if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { + if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e); PreviewCursor.Visible = false; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 21f119d57..2591bdd8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -39,6 +39,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { DocServer } from "../../../DocServer"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); @@ -272,7 +273,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); // OR transparency set let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - col && (Doc.GetProto(newBox).backgroundColor = col); + (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col); let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); round && (Doc.GetProto(newBox).borderRounding = round); @@ -945,17 +946,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); let noteItems: ContextMenuProps[] = []; - noteItems.push({ description: "1: Note", event: () => this.createText("Note", "yellow"), icon: "eye" }); - noteItems.push({ description: "2: Idea", event: () => this.createText("Idea", "pink"), icon: "eye" }); - noteItems.push({ description: "3: Topic", event: () => this.createText("Topic", "lightBlue"), icon: "eye" }); - noteItems.push({ description: "4: Person", event: () => this.createText("Person", "lightGreen"), icon: "eye" }); + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" })); layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }) ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (noteStyle: string, color: string) => { + createText = (noteStyle: number) => { let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - this.addLiveTextBox(Docs.Create.TextDocument({ title: noteStyle, x: pt[0], y: pt[1], autoHeight: true, backgroundColor: color })) + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) }); + text.layout = notes[noteStyle % notes.length]; + this.addLiveTextBox(text); } private childViews = () => [ diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0c4860be1..fe48a3485 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,6 +22,7 @@ import React = require("react"); import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; import { string } from "prop-types"; import { listSpec } from "../../../../new_fields/Schema"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -97,6 +98,11 @@ export class MarqueeView extends React.Component } else if (!e.ctrlKey) { this.props.addLiveTextDocument( Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" })); + } else if (e.keyCode > 48 && e.keyCode <= 57) { + let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); + let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }); + text.layout = notes[(e.keyCode - 49) % notes.length]; + this.props.addLiveTextDocument(text); } e.stopPropagation(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0ea36cdc2..194026a08 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -289,7 +289,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } else if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; if (draggedDoc && draggedDoc.type === DocumentType.TEXT && StrCast(draggedDoc.layout) !== "") { - this.props.Document.layout = draggedDoc; + if (this.props.DataDoc) this.props.DataDoc.layout = draggedDoc; + else this.props.Document.layout = draggedDoc; draggedDoc.isTemplate = true; e.stopPropagation(); } diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 9d35d36d3..af5774ebe 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -7,7 +7,7 @@ import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/nort import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView"; import { CollectionView } from "../../../client/views/collections/CollectionView"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; @@ -50,6 +50,16 @@ export class CurrentUserUtils { doc.workspaces = workspaces; } PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => workspaces && (workspaces.preventTreeViewOpen = true)); + if (doc.noteTypes === undefined) { + let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), + Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), + Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), + Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })]; + const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); + noteTypes.excludeFromLibrary = true; + doc.noteTypes = noteTypes; + } + PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(vals => DocListCast(vals))); if (doc.recentlyClosed === undefined) { const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 }); recentlyClosed.excludeFromLibrary = true; -- cgit v1.2.3-70-g09d2