aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts2
-rw-r--r--src/client/apis/google_docs/GoogleApiClientUtils.ts271
-rw-r--r--src/client/documents/DocumentTypes.ts22
-rw-r--r--src/client/documents/Documents.ts35
-rw-r--r--src/client/util/DictationManager.ts78
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts7
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/views/DocumentDecorations.scss6
-rw-r--r--src/client/views/DocumentDecorations.tsx137
-rw-r--r--src/client/views/EditableView.tsx28
-rw-r--r--src/client/views/GlobalKeyHandler.ts4
-rw-r--r--src/client/views/MainOverlayTextBox.tsx4
-rw-r--r--src/client/views/MainView.tsx74
-rw-r--r--src/client/views/MetadataEntryMenu.scss12
-rw-r--r--src/client/views/MetadataEntryMenu.tsx56
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx36
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx6
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx19
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx7
-rw-r--r--src/client/views/collections/CollectionSubView.tsx21
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx105
-rw-r--r--src/client/views/collections/CollectionView.tsx6
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss74
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx146
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx90
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx20
-rw-r--r--src/client/views/nodes/ButtonBox.tsx27
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx5
-rw-r--r--src/client/views/nodes/DocumentView.tsx131
-rw-r--r--src/client/views/nodes/FieldView.tsx5
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx226
-rw-r--r--src/client/views/nodes/ImageBox.tsx7
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx1
-rw-r--r--src/client/views/nodes/LinkMenu.tsx3
-rw-r--r--src/client/views/nodes/LinkMenuItem.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx529
-rw-r--r--src/client/views/nodes/VideoBox.tsx6
-rw-r--r--src/client/views/pdf/Annotation.tsx6
-rw-r--r--src/client/views/pdf/PDFViewer.tsx1
-rw-r--r--src/client/views/pdf/Page.tsx13
-rw-r--r--src/client/views/presentationview/PresentationElement.tsx614
-rw-r--r--src/client/views/presentationview/PresentationList.tsx52
-rw-r--r--src/client/views/presentationview/PresentationView.scss20
-rw-r--r--src/client/views/presentationview/PresentationView.tsx994
-rw-r--r--src/client/views/search/FilterBox.tsx2
-rw-r--r--src/client/views/search/IconBar.tsx1
-rw-r--r--src/client/views/search/IconButton.tsx2
-rw-r--r--src/client/views/search/SearchBox.tsx1
-rw-r--r--src/client/views/search/SearchItem.tsx3
-rw-r--r--src/new_fields/Doc.ts72
-rw-r--r--src/new_fields/ListSpec.ts0
-rw-r--r--src/new_fields/PresField.ts6
-rw-r--r--src/new_fields/RichTextField.ts50
-rw-r--r--src/new_fields/util.ts3
-rw-r--r--src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloudbin0 -> 172 bytes
-rw-r--r--src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloudbin0 -> 172 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_NewO.docxbin2264571 -> 0 bytes
-rw-r--r--src/scraping/buxton/source/Bill_Notes_OLPC.docxbin6721592 -> 0 bytes
-rw-r--r--src/server/Message.ts1
-rw-r--r--src/server/RouteStore.ts3
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts130
-rw-r--r--src/server/apis/youtube/youtubeApiSample.d.ts (renamed from src/server/youtubeApi/youtubeApiSample.d.ts)0
-rw-r--r--src/server/apis/youtube/youtubeApiSample.js (renamed from src/server/youtubeApi/youtubeApiSample.js)0
-rw-r--r--src/server/authentication/models/current_user_utils.ts26
-rw-r--r--src/server/credentials/google_docs_credentials.json1
-rw-r--r--src/server/credentials/google_docs_token.json1
-rw-r--r--src/server/index.ts67
-rw-r--r--src/server/slides.json10820
74 files changed, 13082 insertions, 2030 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bf5168c22..2cec1046b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -33,6 +33,8 @@ export namespace DocServer {
LivePlayground = 3,
}
+ export let AclsMode = WriteMode.Default;
+
const fieldWriteModes: { [field: string]: WriteMode } = {};
const docsWithUpdates: { [field: string]: Set<Doc> } = {};
diff --git a/src/client/apis/google_docs/GoogleApiClientUtils.ts b/src/client/apis/google_docs/GoogleApiClientUtils.ts
new file mode 100644
index 000000000..798886def
--- /dev/null
+++ b/src/client/apis/google_docs/GoogleApiClientUtils.ts
@@ -0,0 +1,271 @@
+import { docs_v1, slides_v1 } from "googleapis";
+import { PostToServer } from "../../../Utils";
+import { RouteStore } from "../../../server/RouteStore";
+import { Opt } from "../../../new_fields/Doc";
+import { isArray } from "util";
+
+export const Pulls = "googleDocsPullCount";
+export const Pushes = "googleDocsPushCount";
+
+export namespace GoogleApiClientUtils {
+
+ export enum Service {
+ Documents = "Documents",
+ Slides = "Slides"
+ }
+
+ export enum Actions {
+ Create = "create",
+ Retrieve = "retrieve",
+ Update = "update"
+ }
+
+ export enum WriteMode {
+ Insert,
+ Replace
+ }
+
+ export type Identifier = string;
+ export type Reference = Identifier | CreateOptions;
+ export type TextContent = string | string[];
+ export type IdHandler = (id: Identifier) => any;
+ export type CreationResult = Opt<Identifier>;
+ export type ReadLinesResult = Opt<{ title?: string, bodyLines?: string[] }>;
+ 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
+ }
+
+ export interface RetrieveOptions {
+ service: Service;
+ identifier: Identifier;
+ }
+
+ export interface ReadOptions {
+ identifier: Identifier;
+ 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
+ }
+
+ /**
+ * 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<CreationResult> => {
+ 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 {
+
+ export type RetrievalResult = Opt<docs_v1.Schema$Document | slides_v1.Schema$Presentation>;
+ export type UpdateResult = Opt<docs_v1.Schema$BatchUpdateDocumentResponse>;
+
+ export interface UpdateOptions {
+ documentId: Identifier;
+ requests: docs_v1.Schema$Request[];
+ }
+
+ export namespace Utils {
+
+ 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) {
+ if (element.paragraph && element.paragraph.elements) {
+ for (const inner of element.paragraph.elements) {
+ if (inner && inner.textRun) {
+ const fragment = inner.textRun.content;
+ fragment && fragments.push(fragment);
+ }
+ }
+ }
+ }
+ }
+ const text = fragments.join("");
+ return removeNewlines ? text.ReplaceAll("\n", "") : text;
+ };
+
+ 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) {
+ const target = paragraphs[paragraphs.length - 1];
+ if (target.paragraph && target.paragraph.elements) {
+ length = target.paragraph.elements.length;
+ if (length) {
+ const final = target.paragraph.elements[length - 1];
+ return final.endIndex ? final.endIndex - 1 : undefined;
+ }
+ }
+ }
+ }
+ };
+
+ export const initialize = async (reference: Reference) => typeof reference === "string" ? reference : create(reference);
+
+ }
+
+ const KeyMapping = new Map<Service, string>([
+ [Service.Documents, "documentId"],
+ [Service.Slides, "presentationId"]
+ ]);
+
+ export const retrieve = async (options: RetrieveOptions): Promise<RetrievalResult> => {
+ const path = `${RouteStore.googleDocs}/${options.service}/${Actions.Retrieve}`;
+ try {
+ 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;
+ }
+ };
+
+ export const update = async (options: UpdateOptions): Promise<UpdateResult> => {
+ const path = `${RouteStore.googleDocs}/${Service.Documents}/${Actions.Update}`;
+ const parameters = {
+ documentId: options.documentId,
+ requestBody: {
+ requests: options.requests
+ }
+ };
+ try {
+ const replies: UpdateResult = await PostToServer(path, parameters);
+ return replies;
+ } catch {
+ return undefined;
+ }
+ };
+
+ export const read = async (options: ReadOptions): Promise<ReadResult> => {
+ return retrieve({ ...options, service: Service.Documents }).then(document => {
+ let result: ReadResult = {};
+ if (document) {
+ let title = document.title;
+ let body = Utils.extractText(document, options.removeNewlines);
+ result = { title, body };
+ }
+ return result;
+ });
+ };
+
+ export const readLines = async (options: ReadOptions): Promise<ReadLinesResult> => {
+ return retrieve({ ...options, service: Service.Documents }).then(document => {
+ let result: ReadLinesResult = {};
+ if (document) {
+ let title = document.title;
+ let bodyLines = Utils.extractText(document).split("\n");
+ options.removeNewlines && (bodyLines = bodyLines.filter(line => line.length));
+ result = { title, bodyLines };
+ }
+ return result;
+ });
+ };
+
+ export const write = async (options: WriteOptions): Promise<UpdateResult> => {
+ const requests: docs_v1.Schema$Request[] = [];
+ 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({ identifier, service: Service.Documents });
+ if (!schema || !(index = Utils.endOf(schema))) {
+ return undefined;
+ }
+ }
+ if (mode === WriteMode.Replace) {
+ index > 1 && requests.push({
+ deleteContentRange: {
+ range: {
+ startIndex: 1,
+ endIndex: index
+ }
+ }
+ });
+ index = 1;
+ }
+ const text = options.content;
+ text.length && requests.push({
+ insertText: {
+ text: isArray(text) ? text.join("\n") : text,
+ location: { index }
+ }
+ });
+ if (!requests.length) {
+ return undefined;
+ }
+ let replies: any = await update({ documentId: identifier, requests });
+ let errors = "errors";
+ if (errors in replies) {
+ console.log("Write operation failed:");
+ console.log(replies[errors].map((error: any) => error.message));
+ }
+ return replies;
+ };
+
+ }
+
+ export namespace Slides {
+
+ export namespace Utils {
+
+ export const extractTextBoxes = (slides: slides_v1.Schema$Page[]) => {
+ slides.map(slide => {
+ let elements = slide.pageElements;
+ if (elements) {
+ let textboxes: slides_v1.Schema$TextContent[] = [];
+ for (let element of elements) {
+ if (element && element.shape && element.shape.shapeType === "TEXT_BOX" && element.shape.text) {
+ textboxes.push(element.shape.text);
+ }
+ }
+ textboxes.map(text => {
+ if (text.textElements) {
+ text.textElements.map(element => {
+
+ });
+ }
+ if (text.lists) {
+
+ }
+ });
+ }
+ });
+ };
+
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
new file mode 100644
index 000000000..1578e49fe
--- /dev/null
+++ b/src/client/documents/DocumentTypes.ts
@@ -0,0 +1,22 @@
+export enum DocumentType {
+ NONE = "none",
+ TEXT = "text",
+ HIST = "histogram",
+ IMG = "image",
+ WEB = "web",
+ COL = "collection",
+ KVP = "kvp",
+ VID = "video",
+ AUDIO = "audio",
+ PDF = "pdf",
+ ICON = "icon",
+ IMPORT = "import",
+ LINK = "link",
+ LINKDOC = "linkdoc",
+ BUTTON = "button",
+ TEMPLATE = "template",
+ EXTENSION = "extension",
+ YOUTUBE = "youtube",
+ DRAGBOX = "dragbox",
+ PRES = "presentation",
+} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9c8b6c129..47df17329 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,25 +1,3 @@
-export enum DocumentType {
- NONE = "none",
- TEXT = "text",
- HIST = "histogram",
- IMG = "image",
- WEB = "web",
- COL = "collection",
- KVP = "kvp",
- VID = "video",
- AUDIO = "audio",
- PDF = "pdf",
- ICON = "icon",
- IMPORT = "import",
- LINK = "link",
- LINKDOC = "linkdoc",
- BUTTON = "button",
- TEMPLATE = "template",
- EXTENSION = "extension",
- YOUTUBE = "youtube",
- DRAGBOX = "dragbox",
-}
-
import { HistogramField } from "../northstar/dash-fields/HistogramField";
import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
import { HistogramOperation } from "../northstar/operations/HistogramOperation";
@@ -63,8 +41,12 @@ import { Scripting, CompileScript } from "../util/Scripting";
import { ButtonBox } from "../views/nodes/ButtonBox";
import { DragBox } from "../views/nodes/DragBox";
import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderField";
+import { PresBox } from "../views/nodes/PresBox";
import { ComputedField } from "../../new_fields/ScriptField";
import { ProxyField } from "../../new_fields/Proxy";
+import { DocumentType } from "./DocumentTypes";
+//import { PresBox } from "../views/nodes/PresBox";
+//import { PresField } from "../../new_fields/PresField";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -180,6 +162,10 @@ export namespace Docs {
[DocumentType.BUTTON, {
layout: { view: ButtonBox },
}],
+ [DocumentType.PRES, {
+ layout: { view: PresBox },
+ options: {}
+ }],
[DocumentType.DRAGBOX, {
layout: { view: DragBox },
options: { width: 40, height: 40 },
@@ -304,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)) {
@@ -352,6 +338,9 @@ export namespace Docs {
.catch((err: any) => console.log(err));
return inst;
}
+ export function PresDocument(initial: List<Doc> = new List(), options: DocumentOptions = {}) {
+ return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options);
+ }
export function VideoDocument(url: string, options: DocumentOptions = {}) {
return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(new URL(url)), options);
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 9c61fe125..fb3c15cea 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -2,9 +2,10 @@ import { SelectionManager } from "./SelectionManager";
import { DocumentView } from "../views/nodes/DocumentView";
import { UndoManager } from "./UndoManager";
import * as interpreter from "words-to-numbers";
+import { DocumentType } from "../documents/DocumentTypes";
import { Doc } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
-import { Docs, DocumentType } from "../documents/Documents";
+import { Docs } from "../documents/Documents";
import { CollectionViewType } from "../views/collections/CollectionBaseView";
import { Cast, CastCtor } from "../../new_fields/Types";
import { listSpec } from "../../new_fields/Schema";
@@ -12,6 +13,7 @@ import { AudioField, ImageField } from "../../new_fields/URLField";
import { HistogramField } from "../northstar/dash-fields/HistogramField";
import { MainView } from "../views/MainView";
import { Utils } from "../../Utils";
+import { RichTextField } from "../../new_fields/RichTextField";
/**
* This namespace provides a singleton instance of a manager that
@@ -43,7 +45,7 @@ export namespace DictationManager {
export namespace Controls {
- const infringe = "unable to process: dictation manager still involved in previous session";
+ export const Infringed = "unable to process: dictation manager still involved in previous session";
const intraSession = ". ";
const interSession = " ... ";
@@ -62,35 +64,45 @@ export namespace DictationManager {
export type ListeningUIStatus = { interim: boolean } | false;
export interface ListeningOptions {
+ useOverlay: boolean;
language: string;
continuous: ContinuityArgs;
delimiters: DelimiterArgs;
interimHandler: InterimResultHandler;
tryExecute: boolean;
+ terminators: string[];
}
export const listen = async (options?: Partial<ListeningOptions>) => {
let results: string | undefined;
let main = MainView.Instance;
- main.dictationOverlayVisible = true;
- main.isListening = { interim: false };
+ let overlay = options !== undefined && options.useOverlay;
+ if (overlay) {
+ main.dictationOverlayVisible = true;
+ main.isListening = { interim: false };
+ }
try {
results = await listenImpl(options);
if (results) {
Utils.CopyText(results);
- main.isListening = false;
- let execute = options && options.tryExecute;
- main.dictatedPhrase = execute ? results.toLowerCase() : results;
- main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ if (overlay) {
+ main.isListening = false;
+ let execute = options && options.tryExecute;
+ main.dictatedPhrase = execute ? results.toLowerCase() : results;
+ main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ }
+ options && options.tryExecute && await DictationManager.Commands.execute(results);
}
} catch (e) {
- main.isListening = false;
- main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
- main.dictationSuccess = false;
+ if (overlay) {
+ main.isListening = false;
+ main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+ main.dictationSuccess = false;
+ }
} finally {
- main.initiateDictationFade();
+ overlay && main.initiateDictationFade();
}
return results;
@@ -98,7 +110,7 @@ export namespace DictationManager {
const listenImpl = (options?: Partial<ListeningOptions>) => {
if (isListening) {
- return infringe;
+ return Infringed;
}
isListening = true;
@@ -126,6 +138,12 @@ export namespace DictationManager {
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
+ let matchedTerminator: string | undefined;
+ if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+ current = matchedTerminator;
+ recognizer.abort();
+ return complete();
+ }
handler && handler(current);
isManuallyStopped && complete();
};
@@ -161,13 +179,13 @@ export namespace DictationManager {
}
isManuallyStopped = true;
salvageSession ? recognizer.stop() : recognizer.abort();
- let main = MainView.Instance;
- if (main.dictationOverlayVisible) {
- main.cancelDictationFade();
- main.dictationOverlayVisible = false;
- main.dictationSuccess = undefined;
- setTimeout(() => main.dictatedPhrase = placeholder, 500);
- }
+ // let main = MainView.Instance;
+ // if (main.dictationOverlayVisible) {
+ // main.cancelDictationFade();
+ // main.dictationOverlayVisible = false;
+ // main.dictationSuccess = undefined;
+ // setTimeout(() => main.dictatedPhrase = placeholder, 500);
+ // }
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
@@ -299,11 +317,20 @@ export namespace DictationManager {
}
}],
- ["promote", {
+ ["new outline", {
action: (target: DocumentView) => {
- console.log(target);
- },
- restrictTo: [DocumentType.TEXT]
+ let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
+ newBox.autoHeight = true;
+ let proto = newBox.proto!;
+ proto.page = -1;
+ let prompt = "Press alt + r to start dictating here...";
+ let head = 3;
+ let anchor = head + prompt.length;
+ let proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
+ proto.data = new RichTextField(proseMirrorState);
+ proto.backgroundColor = "#eeffff";
+ target.props.addDocTab(newBox, proto, "onRight");
+ }
}]
]);
@@ -317,6 +344,9 @@ export namespace DictationManager {
let what = matches[2];
let dataDoc = Doc.GetProto(target.props.Document);
let fieldKey = "data";
+ if (isNaN(count)) {
+ return;
+ }
for (let i = 0; i < count; i++) {
let created: Doc | undefined;
switch (what) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 7f526b247..124faf266 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -9,6 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView';
import { DocumentView } from '../views/nodes/DocumentView';
import { LinkManager } from './LinkManager';
import { undoBatch, UndoManager } from './UndoManager';
+import { Scripting } from './Scripting';
export class DocumentManager {
@@ -202,4 +203,5 @@ export class DocumentManager {
return 1;
}
}
-} \ No newline at end of file
+}
+Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) }) \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0b6d9b5e5..24c093213 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -9,8 +9,6 @@ import { DocumentManager } from "./DocumentManager";
import { LinkManager } from "./LinkManager";
import { SelectionManager } from "./SelectionManager";
import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import { NumberLiteralType } from "typescript";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(
@@ -142,6 +140,8 @@ export namespace DragManager {
withoutShiftDrag?: boolean;
+ finishDrag?: (dropData: { [id: string]: any }) => void;
+
offsetX?: number;
offsetY?: number;
@@ -211,6 +211,7 @@ export namespace DragManager {
dropAction: dropActionType;
userDropAction: dropActionType;
moveDocument?: MoveFunction;
+ applyAsTemplate?: boolean;
[id: string]: any;
}
@@ -235,7 +236,7 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
runInAction(() => StartDragFunctions.map(func => func()));
- StartDrag(eles, dragData, downX, downY, options,
+ StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
(dropData: { [id: string]: any }) => {
(dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ?
dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index ee623d082..9efef888d 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,7 +4,6 @@ import { DocumentView } from "../views/nodes/DocumentView";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { NumCast, StrCast } from "../../new_fields/Types";
import { InkingControl } from "../views/InkingControl";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
export namespace SelectionManager {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 3627edaae..ac8497bd0 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -264,4 +264,8 @@ $linkGap : 3px;
input {
margin-right: 10px;
}
-} \ No newline at end of file
+}
+
+@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
+@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
+@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index aae7f0d3f..a28088032 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,5 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt, faShare } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
@@ -18,7 +18,7 @@ import { CollectionView } from "./collections/CollectionView";
import './DocumentDecorations.scss';
import { DocumentView, PositionDocument } from "./nodes/DocumentView";
import { FieldView } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
import { IconBox } from "./nodes/IconBox";
import { LinkMenu } from "./nodes/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
@@ -26,10 +26,10 @@ import { Template, Templates } from "./Templates";
import React = require("react");
import { RichTextField } from '../../new_fields/RichTextField';
import { LinkManager } from '../util/LinkManager';
-import { ObjectField } from '../../new_fields/ObjectField';
import { MetadataEntryMenu } from './MetadataEntryMenu';
import { ImageBox } from './nodes/ImageBox';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -37,6 +37,16 @@ export const Flyout = higflyout.default;
library.add(faLink);
library.add(faTag);
library.add(faTimes);
+library.add(faArrowAltCircleDown);
+library.add(faArrowAltCircleUp);
+library.add(faStopCircle);
+library.add(faCheckCircle);
+library.add(faCloudUploadAlt);
+library.add(faSyncAlt);
+library.add(faShare);
+
+const cloud: IconProp = "cloud-upload-alt";
+const fetch: IconProp = "sync-alt";
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -68,6 +78,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@observable public Interacting = false;
@observable private _isMoving = false;
+ @observable public pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable public pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable public pullColor: string = "white";
+ @observable public isAnimatingFetch = false;
+ @observable public openHover = false;
+ public pullColorAnimating = false;
+
+ private pullAnimating = false;
+ private pushAnimating = false;
+
+ public startPullOutcome = action((success: boolean) => {
+ if (!this.pullAnimating) {
+ this.pullAnimating = true;
+ this.pullIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pullIcon = "arrow-alt-circle-down";
+ this.pullAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public startPushOutcome = action((success: boolean) => {
+ if (!this.pushAnimating) {
+ this.pushAnimating = true;
+ this.pushIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pushIcon = "arrow-alt-circle-up";
+ this.pushAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public setPullState = action((unchanged: boolean) => {
+ this.isAnimatingFetch = false;
+ if (!this.pullColorAnimating) {
+ this.pullColorAnimating = true;
+ this.pullColor = unchanged ? "lawngreen" : "red";
+ setTimeout(this.clearPullColor, 1000);
+ }
+ });
+
+ private clearPullColor = action(() => {
+ this.pullColor = "white";
+ this.pullColorAnimating = false;
+ });
+
constructor(props: Readonly<{}>) {
super(props);
DocumentDecorations.Instance = this;
@@ -630,6 +686,77 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
);
}
+ private get targetDoc() {
+ return SelectionManager.SelectedDocuments()[0].props.Document;
+ }
+
+ considerGoogleDocsPush = () => {
+ let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
+ if (!canPush) return (null);
+ let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
+ if (!published) {
+ this.targetDoc.autoHeight = true;
+ }
+ let icon: IconProp = published ? (this.pushIcon as any) : cloud;
+ return (
+ <div className={"linkButtonWrapper"}>
+ <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
+ DocumentDecorations.hasPushedHack = false;
+ this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
+ </div>
+ </div>
+ );
+ }
+
+ considerGoogleDocsPull = () => {
+ let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
+ let dataDoc = Doc.GetProto(this.targetDoc);
+ if (!canPull || !dataDoc[GoogleRef]) return (null);
+ let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
+ icon = this.openHover ? "share" : icon;
+ let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+ let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+ return (
+ <div className={"linkButtonWrapper"}>
+ <div
+ title={title}
+ className="linkButton-linker"
+ style={{
+ backgroundColor: this.pullColor,
+ transition: "0.2s ease all"
+ }}
+ onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
+ onPointerLeave={() => runInAction(() => this.openHover = false)}
+ onClick={e => {
+ if (e.altKey) {
+ e.preventDefault();
+ window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
+ } else {
+ this.clearPullColor();
+ DocumentDecorations.hasPulledHack = false;
+ this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+ }
+ }}>
+ <FontAwesomeIcon
+ style={{
+ WebkitAnimation: animation,
+ MozAnimation: animation
+ }}
+ className="documentdecorations-icon"
+ icon={icon}
+ size="sm"
+ />
+ </div>
+ </div>
+ );
+ }
+
+ public static hasPushedHack = false;
+ public static hasPulledHack = false;
+
considerTooltip = () => {
let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
@@ -782,6 +909,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{this.metadataMenu}
{this.considerEmbed()}
+ {this.considerGoogleDocsPush()}
+ {this.considerGoogleDocsPull()}
{/* {this.considerTooltip()} */}
</div>
</div >
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<EditableProps> {
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
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<EditableProps> {
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<EditableProps> {
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<EditableProps> {
onChange: this.props.autosuggestProps.onChange
}}
/>
- : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
- onBlur={action(() => { 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 }} />;
+ : <input className="editableView-input"
+ defaultValue={this.props.GetValue()}
+ onKeyDown={this.onKeyDown}
+ autoFocus={true}
+ onBlur={e => 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/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 833bacedb..d0464bd5f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -105,7 +105,7 @@ export default class KeyManager {
switch (keyname) {
case " ":
- DictationManager.Controls.listen({ tryExecute: true });
+ DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
stopPropagation = true;
preventDefault = true;
}
@@ -196,8 +196,6 @@ export default class KeyManager {
async printClipboard() {
let text: string = await navigator.clipboard.readText();
- console.log(text)
- console.log(document.activeElement)
}
private ctrl_shift = action((keyname: string) => {
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b14a1e0ea..0839e1114 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -144,9 +144,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
Document={FormattedTextBox.InputBoxOverlay.props.Document}
DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
onClick={undefined}
+ ChromeHeight={this.ChromeHeight}
isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
+ pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
</div>
</div>
</div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 84fdf13a1..f28844009 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
@@ -7,16 +7,15 @@ import "normalize.css";
import * as React from 'react';
import { SketchPicker } from 'react-color';
import Measure from 'react-measure';
-import { Doc, DocListCast, HeightSym, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
+import { List } from '../../new_fields/List';
import { Id } from '../../new_fields/FieldSymbols';
import { InkTool } from '../../new_fields/InkField';
-import { List } from '../../new_fields/List';
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';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
import { ClientUtils } from '../util/ClientUtils';
@@ -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,15 @@ 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 PresModeMenu from './presentationview/PresentationModeMenu';
+import { PresBox } from './nodes/PresBox';
@observer
export class MainView extends React.Component {
public static Instance: MainView;
@observable addMenuToggle = React.createRef<HTMLInputElement>();
- @observable private _workspacesShown: boolean = false;
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
@@ -82,9 +81,6 @@ export class MainView extends React.Component {
public isPointerDown = false;
private set mainContainer(doc: Opt<Doc>) {
if (doc) {
- if (!("presentationView" in doc)) {
- doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
- }
CurrentUserUtils.UserDocument.activeWorkspace = doc;
}
}
@@ -149,6 +145,7 @@ export class MainView extends React.Component {
componentWillUnMount() {
window.removeEventListener("keydown", KeyManager.Instance.handle);
+ //close presentation
window.removeEventListener("pointerdown", this.globalPointerDown);
window.removeEventListener("pointerup", this.globalPointerUp);
}
@@ -174,7 +171,7 @@ export class MainView extends React.Component {
library.add(faCat);
library.add(faFilePdf);
library.add(faObjectGroup);
- library.add(faTable);
+ library.add(faTv);
library.add(faGlobeAsia);
library.add(faUndoAlt);
library.add(faRedoAlt);
@@ -312,7 +309,6 @@ export class MainView extends React.Component {
@computed get dockingContent() {
let flyoutWidth = this.flyoutWidth;
let mainCont = this.mainContainer;
- let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined;
return <Measure offset onResize={this.onResize}>
{({ measureRef }) =>
<div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}>
@@ -321,6 +317,7 @@ export class MainView extends React.Component {
DataDoc={undefined}
addDocument={undefined}
addDocTab={emptyFunction}
+ pinToPres={emptyFunction}
onClick={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
@@ -338,7 +335,6 @@ export class MainView extends React.Component {
zoomToScale={emptyFunction}
getScale={returnOne}
/>}
- {castRes ? <PresentationView Documents={castRes} key="presentation" /> : null}
</div>
}
</Measure>;
@@ -385,6 +381,7 @@ export class MainView extends React.Component {
DataDoc={undefined}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
removeDocument={undefined}
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
@@ -442,44 +439,27 @@ 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<Doc>(), { 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<HTMLDivElement>, IconName, string, () => Doc][] = [
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "tv", "Add Presentation Trail", addPresNode],
[React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
[React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
- // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
- [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode],
- [React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
- [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode]
+ [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
+ [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
+ //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
];
if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
- const setWriteMode = (mode: DocServer.WriteMode) => {
- console.log(DocServer.WriteMode[mode]);
- const mode1 = mode;
- const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- DocServer.setFieldWriteMode("x", mode1);
- DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("width", mode1);
- DocServer.setFieldWriteMode("height", mode1);
-
- DocServer.setFieldWriteMode("panX", mode2);
- DocServer.setFieldWriteMode("panY", mode2);
- DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("viewType", mode2);
- };
return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} >
<input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
@@ -488,7 +468,6 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
<li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
- <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
<li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
<li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
{btns.map(btn =>
@@ -497,13 +476,6 @@ export class MainView extends React.Component {
<FontAwesomeIcon icon={btn[1]} size="sm" />
</button>
</div></li>)}
- <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
- {ClientUtils.RELEASE ? [] : [
- <li key="test"><button className="add-button round-button" title="Default" onClick={() => setWriteMode(DocServer.WriteMode.Default)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test1"><button className="add-button round-button" title="Playground" onClick={() => setWriteMode(DocServer.WriteMode.Playground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test2"><button className="add-button round-button" title="Live Playground" onClick={() => setWriteMode(DocServer.WriteMode.LivePlayground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test3"><button className="add-button round-button" title="Live Readonly" onClick={() => setWriteMode(DocServer.WriteMode.LiveReadonly)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
- ]}
<li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
<div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
<SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -542,7 +514,6 @@ export class MainView extends React.Component {
@observable isSearchVisible = false;
@action.bound
toggleSearch = () => {
- // console.log("search toggling")
this.isSearchVisible = !this.isSearchVisible;
}
@@ -571,12 +542,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) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>;
+ }
+
render() {
return (
<div id="main-div">
{this.dictationOverlay}
<DocumentDecorations />
{this.mainContent}
+ {this.miniPresentation}
<PreviewCursor />
<ContextMenu />
{this.nodesMenu()}
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index a33f24bcc..7da55fd1c 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -1,12 +1,24 @@
.metadataEntry-outerDiv {
display: flex;
width: 310px;
+ flex-direction: column;
input[type=checkbox] {
margin-left: 5px;
}
}
+.metadataEntry-keys {
+ max-height: 80;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+.metadataEntry-inputArea {
+ display:flex;
+ flex-direction: row;
+}
+
.react-autosuggest__container {
position: relative;
}
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index df6b7f721..f1b101b8e 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -1,12 +1,12 @@
import * as React from 'react';
import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
-import { observable, action, runInAction, trace } from 'mobx';
+import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc';
import * as Autosuggest from 'react-autosuggest';
import { undoBatch } from '../util/UndoManager';
-import { child } from 'serializr';
+import { emptyFunction } from '../../Utils';
export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
export interface MetadataEntryProps {
@@ -21,6 +21,8 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
@observable private _currentValue: string = "";
@observable private suggestions: string[] = [];
private _addChildren: boolean = false;
+ @observable _allSuggestions: string[] = [];
+ _suggestionDispser: IReactionDisposer | undefined;
private userModified = false;
private autosuggestRef = React.createRef<Autosuggest>();
@@ -153,19 +155,16 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
getSuggestionValue = (suggestion: string) => suggestion;
renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
+ return (null);
}
+ componentDidMount() {
- onSuggestionFetch = async ({ value }: { value: string }) => {
- const sugg = await this.getKeySuggestions(value);
- runInAction(() => {
- this.suggestions = sugg;
- });
+ this._suggestionDispser = reaction(() => this._currentKey,
+ () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)),
+ { fireImmediately: true });
}
-
- @action
- onSuggestionClear = () => {
- this.suggestions = [];
+ componentWillUnmount() {
+ this._suggestionDispser && this._suggestionDispser();
}
onClick = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -198,19 +197,26 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
render() {
return (
<div className="metadataEntry-outerDiv">
- Key:
- <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={this.suggestions}
- alwaysRenderSuggestions
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={this.onSuggestionFetch}
- onSuggestionsClearRequested={this.onSuggestionClear}
- ref={this.autosuggestRef} />
- Value:
- <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
- {this.considerChildOptions}
- </div >
+ <div className="metadataEntry-inputArea">
+ Key:
+ <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+ getSuggestionValue={this.getSuggestionValue}
+ suggestions={[]}
+ alwaysRenderSuggestions={false}
+ renderSuggestion={this.renderSuggestion}
+ onSuggestionsFetchRequested={emptyFunction}
+ onSuggestionsClearRequested={emptyFunction}
+ ref={this.autosuggestRef} />
+ Value:
+ <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+ {this.considerChildOptions}
+ </div>
+ <div className="metadataEntry-keys" >
+ <ul>
+ {this._allSuggestions.slice().sort().map(s => <li key={s} onClick={action(() => { this._currentKey = s; this.previewValue(); })} >{s}</li>)}
+ </ul>
+ </div>
+ </div>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a8e723379..dc0cbda0b 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
@@ -162,6 +164,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
+ public Has = (document: Doc) => {
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ if (!docs) {
+ return false;
+ }
+ return docs.includes(document);
+ }
+
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@@ -535,6 +545,27 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}));
}
+ /**
+ * Adds a document to the presentation view
+ **/
+ @undoBatch
+ @action
+ public PinDoc(doc: Doc) {
+ //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]);
+ }
+ if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ this.addDocTab(curPres, undefined, "onRight");
+ }
+ }
+ }
+
componentDidMount() {
this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
this.props.glContainer.on("tab", this.onActiveContentItemChanged);
@@ -569,7 +600,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
ScreenToLocalTransform = () => {
- if (this._mainCont && this._mainCont!.children) {
+ if (this._mainCont && this._mainCont.children) {
let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
scale = Utils.GetScreenTransform(this._mainCont).scale;
return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
@@ -583,6 +614,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
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);
}
@@ -609,6 +642,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
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<CellProps> {
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..4008dea51 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -171,6 +171,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={this.setPreviewScript}
previewScript={this.previewScript}
/>
@@ -200,6 +201,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
active={this.props.active}
onDrop={this.onDrop}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
isSelected={this.props.isSelected}
isFocused={this.isFocused}
setFocused={this.setFocused}
@@ -251,6 +253,7 @@ export interface SchemaTableProps {
active: () => boolean;
onDrop: (e: React.DragEvent<Element>, 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 +380,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
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 +911,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 +1002,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
whenActiveChanged={this.props.whenActiveChanged}
ContainingCollectionView={this.props.CollectionView}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
parentActive={this.props.active}
ScreenToLocalTransform={this.getTransform}
renderDepth={this.props.renderDepth + 1}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2e4f6aff5..4ab656744 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
import { emptyFunction, Utils, numberRange } from "../../../Utils";
-import { DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -155,11 +155,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={emptyFunction}
previewScript={undefined}>
</CollectionSchemaPreview>;
}
- getDocHeight(d: Doc) {
+ getDocHeight(d?: Doc) {
+ if (!d) return 0;
let nw = NumCast(d.nativeWidth);
let nh = NumCast(d.nativeHeight);
if (!d.ignoreAspect && nw && nh) {
@@ -285,15 +287,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
masonryChildren(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
+ const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let dref = React.createRef<HTMLDivElement>();
- 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 height = () => this.getDocHeight(pair.layout);
+ let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
this._docXfs.push({ dxf: dxf, width: width, height: height });
- return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
- {this.getDisplayDoc(layoutDoc, d, dxf, width)}
+ return !pair.layout ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+ {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
</div>;
});
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 74c7ef305..2536eff00 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -78,11 +78,14 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
let parent = this.props.parent;
parent._docXfs.length = 0;
return docs.map((d, i) => {
- let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+ const pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let width = () => 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 dref = React.createRef<HTMLDivElement>();
- 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/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 077f3f941..99e5ab7b3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -11,12 +11,13 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
import { RouteStore } from "../../../server/RouteStore";
import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Docs, DocumentOptions } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
@@ -82,7 +83,7 @@ export function CollectionSubView<T>(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);
@@ -112,7 +113,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
this.childDocs.map(doc =>
Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined)
@@ -206,7 +207,17 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
return;
}
-
+ let matches: RegExpExecArray | null;
+ if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+ let newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
+ let proto = newBox.proto!;
+ proto.autoHeight = true;
+ proto[GoogleRef] = matches[2];
+ proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+ proto.backgroundColor = "#eeeeff";
+ this.props.addDocument(newBox);
+ return;
+ }
let batch = UndoManager.StartBatch("collection view drop");
let promises: Promise<void>[] = [];
// tslint:disable-next-line:prefer-for-of
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4b1fca18a..7e1aacd5d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,8 @@ import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { emptyFunction, Utils } from '../../../Utils';
-import { Docs, DocUtils, DocumentType } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
@@ -27,6 +28,7 @@ import "./CollectionTreeView.scss";
import React = require("react");
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { ContextMenuProps } from '../ContextMenuItem';
export interface TreeViewProps {
@@ -38,6 +40,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;
@@ -47,6 +50,8 @@ export interface TreeViewProps {
treeViewId: string;
parentKey: string;
active: () => boolean;
+ showHeaderFields: () => boolean;
+ preventTreeViewOpen: boolean;
}
library.add(faTrashAlt);
@@ -63,7 +68,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<TreeViewProps> {
static loadId = "";
@@ -71,7 +81,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
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; }
@@ -144,7 +156,7 @@ class TreeView extends React.Component<TreeViewProps> {
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;
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";
@@ -161,20 +173,21 @@ class TreeView extends React.Component<TreeViewProps> {
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<string>([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 => {
if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+ ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
@@ -198,7 +211,7 @@ class TreeView extends React.Component<TreeViewProps> {
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;
@@ -252,16 +265,16 @@ class TreeView extends React.Component<TreeViewProps> {
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
let rows: JSX.Element[] = [];
- for (let key of Object.keys(ids).sort()) {
+ 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);
+ 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.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 = <EditableView
key="editableView"
@@ -286,14 +299,14 @@ class TreeView extends React.Component<TreeViewProps> {
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, true));
let docs = expandKey === "links" ? this.childLinks : this.childDocs;
return <ul key={expandKey + "more"}>
{!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.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)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -318,6 +331,7 @@ class TreeView extends React.Component<TreeViewProps> {
active={this.props.active}
whenActiveChanged={emptyFunction as any}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={emptyFunction}>
</CollectionSchemaPreview>
</div>;
@@ -326,8 +340,8 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={this._collapsed ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ return <div className="bullet" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
</div>;
}
/**
@@ -341,13 +355,13 @@ class TreeView extends React.Component<TreeViewProps> {
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- 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}
</span>);
@@ -365,7 +379,7 @@ class TreeView extends React.Component<TreeViewProps> {
}} >
{this.editableView("title")}
</div >
- {headerElements}
+ {this.props.showHeaderFields() ? headerElements : (null)}
{openRight}
</>;
}
@@ -378,13 +392,13 @@ class TreeView extends React.Component<TreeViewProps> {
{this.renderTitle}
</div>
<div className="treeViewItem-border">
- {this._collapsed ? (null) : this.renderContent}
+ {!this.treeViewOpen ? (null) : this.renderContent}
</div>
</li>
</div>;
}
public static GetChildElements(
- docs: Doc[],
+ docList: Doc[],
treeViewId: string,
containingCollection: Doc,
dataDoc: Doc | undefined,
@@ -394,12 +408,16 @@ class TreeView extends React.Component<TreeViewProps> {
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,
panelWidth: () => number,
- renderDepth: number
+ renderDepth: number,
+ showHeaderFields: () => boolean,
+ preventTreeViewOpen: boolean
) {
+ let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0);
let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
let script = viewSpecScript.script;
@@ -414,8 +432,8 @@ class TreeView extends React.Component<TreeViewProps> {
});
}
- let descending = BoolCast(containingCollection.stackingHeadersSortDescending);
- docs.sort(function (a, b): 1 | -1 {
+ let descending = BoolCast(containingCollection.stackingHeadersSortDescending, true);
+ docs.slice().sort(function (a, b): 1 | -1 {
let descA = descending ? b : a;
let descB = descending ? a : b;
let first = descA[String(containingCollection.sectionFilter)];
@@ -439,13 +457,19 @@ class TreeView extends React.Component<TreeViewProps> {
let rowWidth = () => panelWidth() - 20;
return docs.map((child, i) => {
let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
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) => {
@@ -470,10 +494,13 @@ class TreeView extends React.Component<TreeViewProps> {
moveDocument={move}
dropAction={dropAction}
addDocTab={addDocTab}
+ pinToPres={pinToPres}
ScreenToLocalTransform={screenToLocalXf}
outerXf={outerXf}
parentKey={key}
- active={active} />;
+ active={active}
+ showHeaderFields={showHeaderFields}
+ preventTreeViewOpen={preventTreeViewOpen} />;
});
}
}
@@ -515,6 +542,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ } else {
+ let layoutItems: ContextMenuProps[] = [];
+ layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
+ ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
@@ -553,7 +584,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, 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) : (
<div id="body" className="collectionTreeView-dropTarget"
@@ -567,20 +598,22 @@ export class CollectionTreeView extends CollectionSubView(Document) {
display={"block"}
height={72}
GetValue={() => 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<string>([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, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true));
+ })} />
{this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
{this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
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, () => this.props.Document.chromeStatus !== "disabled",
+ BoolCast(this.props.Document.preventTreeViewOpen))
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e1adaa19..3a8253720 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -108,9 +108,13 @@ export class CollectionView extends React.Component<FieldViewProps> {
ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
let existing = ContextMenu.Instance.findByDescription("Layout...");
let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: "Create Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
+
+ let makes = ContextMenu.Instance.findByDescription("Make...");
+ let makeItems: ContextMenuProps[] = makes && "subitems" in makes ? makes.subitems : [];
+ makeItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
+ !makes && ContextMenu.Instance.addItem({ description: "Make...", subitems: makeItems, icon: "hand-point-right" });
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 595f079d1..64411b5fe 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -7,7 +7,6 @@
z-index: 9001;
transition: top .5s;
background: lightgrey;
- padding: 10px;
.collectionViewChrome {
display: grid;
@@ -65,7 +64,7 @@
font-size: 75%;
background: rgb(238, 238, 238);
height: 100%;
- width: 150px;
+ width: 75px;
}
.collectionViewBaseChrome-viewSpecsMenu {
@@ -235,4 +234,75 @@
margin-left: 50px;
}
}
+}
+
+
+.commandEntry-outerDiv {
+ display: flex;
+ flex-direction: column;
+ width: 165px;
+ height: 40px;
+}
+.commandEntry-inputArea {
+ display:flex;
+ flex-direction: row;
+ width: 150px;
+ margin: auto 0 auto auto;
+}
+
+.react-autosuggest__container {
+ position: relative;
+ width: 100%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.react-autosuggest__input {
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ width: 100%;
+}
+
+.react-autosuggest__input--focused {
+ outline: none;
+}
+
+.react-autosuggest__input--open {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.react-autosuggest__suggestions-container {
+ display: none;
+}
+
+.react-autosuggest__suggestions-container--open {
+ display: block;
+ position: fixed;
+ overflow-y: auto;
+ max-height: 400px;
+ width: 180px;
+ border: 1px solid #aaa;
+ background-color: #fff;
+ font-family: Helvetica, sans-serif;
+ font-weight: 300;
+ font-size: 16px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ z-index: 2;
+}
+
+.react-autosuggest__suggestions-list {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.react-autosuggest__suggestion {
+ cursor: pointer;
+ padding: 10px 20px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+ background-color: #ddd;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 6ea718330..352bcd4a6 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -1,30 +1,27 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
import * as React from "react";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
-import { CollectionViewType } from "./CollectionBaseView";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils, emptyFunction } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { CompileScript } from "../../util/Scripting";
import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc";
+import { EditableView } from "../EditableView";
+import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
import { DocLike } from "../MetadataEntryMenu";
+import { CollectionViewType } from "./CollectionBaseView";
+import { CollectionView } from "./CollectionView";
+import "./CollectionViewChromes.scss";
import * as Autosuggest from 'react-autosuggest';
-import { EditableView } from "../EditableView";
-import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Utils } from "../../../Utils";
import KeyRestrictionRow from "./KeyRestrictionRow";
-import { CompileScript } from "../../util/Scripting";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { listSpec } from "../../../new_fields/Schema";
-import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { threadId } from "worker_threads";
-import { DragManager } from "../../util/DragManager";
+import { Docs } from "../../documents/Documents";
const datepicker = require('js-datepicker');
-import * as $ from 'jquery';
-import { firebasedynamiclinks } from "googleapis/build/src/apis/firebasedynamiclinks";
interface CollectionViewChromeProps {
CollectionView: CollectionView;
@@ -48,10 +45,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@observable private _dateWithinValue: string = "";
@observable private _dateValue: Date | string = "";
@observable private _keyRestrictions: [JSX.Element, string][] = [];
+ @observable private suggestions: string[] = [];
+ _commandRef = React.createRef<HTMLInputElement>();
@computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
private _picker: any;
- private _datePickerElGuid = Utils.GenerateGuid();
getFilters = (script: string) => {
let re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g;
@@ -91,11 +89,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
componentDidMount = () => {
- setTimeout(() => this._picker = datepicker("#" + this._datePickerElGuid, {
- disabler: (date: Date) => date > new Date(),
- onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
- dateSelected: new Date()
- }), 1000);
let fields: Filter[] = [];
if (this.filterValue) {
@@ -173,8 +166,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this.openViewSpecs(e);
- console.log(this._keyRestrictions)
-
let keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
let yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
let monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
@@ -300,7 +291,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.draggedDocuments.length) {
- this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0];
+ if (this._currentKey === "Set Template") {
+ this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0];
+ }
+ if (this._currentKey === "Set Content") {
+ Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(de.data.draggedDocuments);
+ }
e.stopPropagation();
return true;
}
@@ -308,6 +304,76 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
return true;
}
+ datePickerRef = (node: HTMLInputElement) => {
+ if (node) {
+ try {
+ this._picker = datepicker("#" + node.id, {
+ disabler: (date: Date) => date > new Date(),
+ onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+ dateSelected: new Date()
+ });
+ } catch (e) {
+ console.log("date picker exception:" + e);
+ }
+ }
+ }
+
+ @observable private _currentKey: string = "";
+ @observable _allCommands: string[] = ["Set Template", "Set Content"];
+ private autosuggestRef = React.createRef<Autosuggest>();
+
+ renderSuggestion = (suggestion: string) => {
+ return <p>{suggestion}</p>;
+ }
+ getSuggestionValue = (suggestion: string) => suggestion;
+
+ @action
+ onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+ this._currentKey = newValue;
+ }
+ onSuggestionFetch = async ({ value }: { value: string }) => {
+ const sugg = await this.getKeySuggestions(value);
+ runInAction(() => this.suggestions = sugg);
+ }
+ @action
+ onSuggestionClear = () => {
+ this.suggestions = [];
+ }
+ getKeySuggestions = async (value: string): Promise<string[]> => {
+ return this._allCommands.filter(c => c.indexOf(value) !== -1);
+ }
+
+ autoSuggestDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ }
+ dragCommandDown = (e: React.PointerEvent) => {
+ let de = new DragManager.DocumentDragData([this.props.CollectionView.props.Document], [undefined]);
+ DragManager.StartDocumentDrag([this._commandRef.current!], de, e.clientX, e.clientY, {
+ finishDrag: (dropData: { [id: string]: any }) => {
+ let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: this._currentKey });
+ let script = `getProto(target).data = copyField(this.source);`;
+ let compiled = CompileScript(script, {
+ params: { doc: Doc.name },
+ capturedVariables: { target: this.props.CollectionView.props.Document },
+ typecheck: false,
+ editable: true
+ });
+ if (compiled.compiled) {
+ let scriptField = new ScriptField(compiled);
+ bd.onClick = scriptField;
+ }
+ dropData.droppedDocuments = [bd];
+ },
+ handlers: {
+ dragComplete: action(() => {
+ }),
+ },
+ hideSource: false
+ });
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
render() {
let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
return (
@@ -337,7 +403,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</select>
<div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
<input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="FILTER DOCUMENTS"
+ placeholder="FILTER"
value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
onChange={(e) => { }}
onPointerDown={this.openViewSpecs}
@@ -368,7 +434,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<option value="1y">1 year of</option>
</select>
<input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- id={this._datePickerElGuid}
+ id={Utils.GenerateGuid()}
+ ref={this.datePickerRef}
value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
onPointerDown={this.openDatePicker}
@@ -387,8 +454,19 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</div>
</div>
</div>
- <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{}}>
- TEMPLATE
+ <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
+ <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
+ <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+ getSuggestionValue={this.getSuggestionValue}
+ suggestions={this.suggestions}
+ alwaysRenderSuggestions={true}
+ renderSuggestion={this.renderSuggestion}
+ onSuggestionsFetchRequested={this.onSuggestionFetch}
+ onSuggestionsClearRequested={this.onSuggestionClear}
+ ref={this.autosuggestRef} />
+ </div>
+ </div>
</div>
</div>
{this.subChrome()}
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<CollectionV
ctx.stroke();
// ctx.font = "10px Arial";
- // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+ // ctx.fillText(Doc.CurrentUserEmail[0].toUpperCase(), 10, 10);
}
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6320cb3d5..224e8047d 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,14 +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 { DocumentType, Docs } from "../../../documents/Documents";
+import { DocServer } from "../../../DocServer";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
@@ -344,9 +345,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, -1);
if (cluster !== -1) {
let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+
+ // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view
+ let prevSelected = SelectionManager.SelectedDocuments();
this.selectDocuments(eles);
let clusterDocs = SelectionManager.SelectedDocuments();
SelectionManager.DeselectAll();
+ prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
+
let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
de.moveDocument = this.props.moveDocument;
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
@@ -621,20 +627,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getScale = () => this.Document.scale ? this.Document.scale : 1;
- getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
- let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
+ getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
- DataDoc: pair.data,
- Document: pair.layout,
+ DataDoc: childData,
+ Document: childLayout,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
onClick: this.props.onClick,
- ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
+ ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
- selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
- PanelWidth: pair.layout[WidthSym],
- PanelHeight: pair.layout[HeightSym],
+ selectOnLoad: childLayout[Id] === this._selectOnLoaded,
+ PanelWidth: childLayout[WidthSym],
+ PanelHeight: childLayout[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
@@ -643,6 +648,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
@@ -668,6 +674,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
@@ -742,12 +749,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
{ x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
state = pos.state === undefined ? state : pos.state;
- prev.push({
- ele: <CollectionFreeFormDocumentView key={doc[Id]}
- x={script ? pos.x : undefined} y={script ? pos.y : undefined}
- width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
- bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
- });
+ let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc);
+ if (pair.layout && !(pair.data instanceof Promise)) {
+ prev.push({
+ ele: <CollectionFreeFormDocumentView key={doc[Id]}
+ x={script ? pos.x : undefined} y={script ? pos.y : undefined}
+ width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
+ });
+ }
}
}
return prev;
@@ -813,6 +823,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onContextMenu = (e: React.MouseEvent) => {
let layoutItems: ContextMenuProps[] = [];
+ layoutItems.push({
+ 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();
+ }
+ });
layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
layoutItems.push({
@@ -830,12 +870,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
});
layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
-
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- 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" });
+ layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index aad26efa0..221237365 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -373,7 +373,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
let selection: Doc[] = [];
- this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
+ this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -383,7 +383,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
if (!selection.length && selectBackgrounds) {
- this.props.activeDocuments().map(doc => {
+ this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -393,6 +393,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
}
+ if (!selection.length) {
+ let left = this._downX < this._lastX ? this._downX : this._lastX;
+ let top = this._downY < this._lastY ? this._downY : this._lastY;
+ let topLeft = this.props.getContainerTransform().transformPoint(left, top);
+ let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
+ var x = NumCast(doc.x);
+ var y = NumCast(doc.y);
+ var w = NumCast(doc.width);
+ var h = NumCast(doc.height);
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
+ selection.push(doc);
+ }
+ });
+ }
return selection;
}
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index 8b6f11aac..ca5f0acc2 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -15,7 +15,11 @@ import { Doc } from '../../../new_fields/Doc';
import './ButtonBox.scss';
import { observer } from 'mobx-react';
import { DocumentIconContainer } from './DocumentIcon';
-import { StrCast } from '../../../new_fields/Types';
+import { StrCast, BoolCast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
+import { action, computed } from 'mobx';
+import { List } from '../../../new_fields/List';
library.add(faEdit as any);
@@ -30,10 +34,29 @@ const ButtonDocument = makeInterface(ButtonSchema);
@observer
export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
public static LayoutString() { return FieldView.LayoutString(ButtonBox); }
+ private dropDisposer?: DragManager.DragDropDisposer;
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+
+
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ Doc.GetProto(this.dataDoc).source = new List<Doc>(de.data.droppedDocuments);
+ }
+ }
render() {
return (
- <div className="buttonBox-outerDiv" >
+ <div className="buttonBox-outerDiv" ref={this.createDropTarget} >
<div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} >{this.Document.text || this.Document.title}</div>
</div>
);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ee596c841..7631ecc6c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -83,7 +83,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
transformOrigin: "left top",
position: "absolute",
backgroundColor: "transparent",
- boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
+ boxShadow: this.props.Document.opacity === 0 ? undefined : this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
this.clusterColor ? (
this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
`${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c73e960d8..d77662355 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -13,6 +13,7 @@ import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { DragBox } from "./DragBox";
import { ButtonBox } from "./ButtonBox";
+import { PresBox } from "./PresBox";
import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
@@ -67,7 +68,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
get dataDoc() {
- if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+ if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
// if there is no dataDoc (ie, we're not rendering a template layout), but this document
// has a template layout document, then we will render the template layout but use
// this document as the data document for the layout.
@@ -107,7 +108,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
blacklistedAttrs={[]}
- components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, YoutubeBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6f5235c4a..6c944c6b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -33,7 +33,6 @@ import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { MainView } from '../MainView';
import { OverlayView } from '../OverlayView';
-import { PresentationView } from "../presentationview/PresentationView";
import { ScriptBox } from '../ScriptBox';
import { ScriptingRepl } from '../ScriptingRepl';
import { Template } from "./../Templates";
@@ -41,7 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import { FormattedTextBox } from './FormattedTextBox';
import React = require("react");
-import { IDisposable } from '../../northstar/utils/IDisposable';
+import { DocumentType } from '../../documents/DocumentTypes';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
library.add(fa.faTrash);
@@ -98,6 +97,7 @@ export interface DocumentViewProps {
whenActiveChanged: (isActive: boolean) => 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;
@@ -139,6 +139,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _lastTap: number = 0;
private _doubleTap = false;
private _hitExpander = false;
+ private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
_animateToIconDisposer?: IReactionDisposer;
@@ -228,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
get dataDoc() {
- if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+ if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
// if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
// has a template layout document, then we will render the template layout but use
// this document as the data document for the layout.
@@ -236,7 +237,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
}
- startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
+ startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) {
if (this._mainCont.current) {
let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
@@ -247,6 +248,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.xOffset = xoff;
dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
+ dragData.applyAsTemplate = applyAsTemplate;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
dragComplete: action(emptyFunction)
@@ -293,8 +295,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClick = async (e: React.MouseEvent) => {
if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing.
- e.stopPropagation();
if (this.onClickHandler && this.onClickHandler.script) {
+ e.stopPropagation();
this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document });
e.preventDefault();
return;
@@ -302,6 +304,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let altKey = e.altKey;
let ctrlKey = e.ctrlKey;
if (this._doubleTap && this.props.renderDepth) {
+ e.stopPropagation();
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
fullScreenAlias.templates = new List<string>();
Doc.UseDetailLayout(fullScreenAlias);
@@ -313,10 +316,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ if (BoolCast(this.props.Document.ignoreClick)) {
+ return;
+ }
+ e.stopPropagation();
SelectionManager.SelectDoc(this, e.ctrlKey);
let isExpander = (e.target as any).id === "isExpander";
- if (BoolCast(this.props.Document.isButton) || isExpander) {
- SelectionManager.DeselectAll();
+ if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) {
let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
@@ -327,6 +333,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
// let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ SelectionManager.DeselectAll();
let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
if (altKey || ctrlKey) {
@@ -355,6 +362,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
else if (linkedDocs.length) {
+ SelectionManager.DeselectAll();
let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document));
let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
@@ -377,29 +385,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
}
+
+
onPointerDown = (e: React.PointerEvent): void => {
if (e.nativeEvent.cancelBubble) return;
this._downX = e.clientX;
this._downY = e.clientY;
this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
- // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
- // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
- // e.stopPropagation();
- // } else {
+ this._hitTemplateDrag = false;
+ for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+ if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+ this._hitTemplateDrag = true;
+ }
+ }
if (this.active) e.stopPropagation(); // events stop at the lowest document that is active.
document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
- // }
}
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble && this.active) {
+ if (e.cancelBubble && this.active) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+ else if (!e.cancelBubble && this.active) {
if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) {
if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag);
}
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
@@ -457,6 +471,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
}
+ if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
+ Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc);
+ e.stopPropagation();
+ }
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.Document;
@@ -573,23 +591,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
let existingMake = ContextMenu.Instance.findByDescription("Make...");
let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
- makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
- makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
- makes.push({ description: "Edit OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") });
+ makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: this.props.Document.lockedPosition ? "unlock" : "lock" });
+ makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" });
+ makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") });
makes.push({
- description: "Make Portal", event: () => {
+ description: "Into Portal", event: () => {
let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" });
- //Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]);
- //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
- //Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]);
- //let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" });
- //this.props.addDocument && this.props.addDocument(coll);
- //this.props.removeDocument && this.props.removeDocument(this.props.Document);
DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal");
this.makeBtnClicked();
-
}, icon: "window-restore"
});
+ makes.push({ description: this.props.Document.ignoreClick ? "Selectable" : "Unselectable", event: () => this.props.Document.ignoreClick = !this.props.Document.ignoreClick, icon: this.props.Document.ignoreClick ? "unlock" : "lock" })
!existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
let existing = ContextMenu.Instance.findByDescription("Layout...");
let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
@@ -614,7 +626,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(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: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
+ 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(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({
description: "Download document", icon: "download", event: () => {
@@ -626,42 +638,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(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[] = [];
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) {
@@ -684,6 +667,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
runInAction(() => {
cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
+ if (!ClientUtils.RELEASE) {
+ let setWriteMode = (mode: DocServer.WriteMode) => {
+ DocServer.AclsMode = mode;
+ const mode1 = mode;
+ const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ DocServer.setFieldWriteMode("x", mode1);
+ DocServer.setFieldWriteMode("y", mode1);
+ DocServer.setFieldWriteMode("width", mode1);
+ DocServer.setFieldWriteMode("height", mode1);
+
+ DocServer.setFieldWriteMode("panX", mode2);
+ DocServer.setFieldWriteMode("panY", mode2);
+ DocServer.setFieldWriteMode("scale", mode2);
+ DocServer.setFieldWriteMode("viewType", mode2);
+ };
+ let aclsMenu: ContextMenuProps[] = [];
+ aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
+ aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
+ cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
+ cm.addItem({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+ }
+
if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
@@ -718,7 +725,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
chromeHeight = () => {
let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined;
let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
- return showTitle ? 25 : 0;
+ let templates = Cast(this.layoutDoc.templates, listSpec("string"));
+ if (!showOverlays && templates instanceof List) {
+ templates.map(str => {
+ if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
+ });
+ }
+ return (showTitle ? 25 : 0) + 1;// bcz: why 8??
}
get layoutDoc() {
@@ -772,7 +785,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
{!showTitle && !showCaption ? this.contents :
<div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
- <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
+ <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 29px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
{this.contents}
</div>
{!showTitle ? (null) :
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 3287949e2..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;
@@ -57,6 +58,7 @@ export interface FieldViewProps {
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string = "data", fieldExt: string = "") {
return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} fieldExt={"${fieldExt}"} />`;
+ //"<ImageBox {...props} />"
}
@computed
@@ -78,6 +80,9 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} leaveNativeSize={true} />;
}
+ // else if (field instaceof PresBox) {
+ // return <PresBox {...this.props} />;
+ // }
else if (field instanceof IconField) {
return <IconBox {...this.props} />;
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index f7890e5a6..5492a457d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,20 +1,20 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx";
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
-import { EditorState, Plugin, Transaction } from "prosemirror-state";
+import { EditorState, Plugin, Transaction, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
-import { RichTextField } from "../../../new_fields/RichTextField";
+import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
+import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
import { Utils } from '../../../Utils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
@@ -29,17 +29,23 @@ import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
-import { MainOverlayTextBox } from '../MainOverlayTextBox';
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
+import { DocumentDecorations } from '../DocumentDecorations';
+import { MainOverlayTextBox } from '../MainOverlayTextBox';
+import { DictationManager } from '../../util/DictationManager';
+import { ReplaceStep } from 'prosemirror-transform';
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
//
+export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+
export interface FormattedTextBoxProps {
isOverlay?: boolean;
hideOnLeave?: boolean;
@@ -53,9 +59,13 @@ const richTextSchema = createSchema({
documentText: "string"
});
+export const GoogleRef = "googleDocId";
+
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
+type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void;
+
@observer
export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string = "data") {
@@ -73,6 +83,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private _textReactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
+ private pullReactionDisposer: Opt<IReactionDisposer>;
+ private pushReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@observable _entered = false;
@@ -167,9 +179,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
const marks = tx.storedMarks;
- console.log(marks);
if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
}
+
this._applyingChange = true;
const fieldkey = "preview";
if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
@@ -255,6 +267,64 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ recordKeyHandler = (e: KeyboardEvent) => {
+ if (this.props.Document !== SelectionManager.SelectedDocuments()[0].props.Document) {
+ return;
+ }
+ if (e.key === "R" && e.altKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.recordBullet();
+ }
+ }
+
+ recordBullet = async () => {
+ let completedCue = "end session";
+ let results = await DictationManager.Controls.listen({
+ interimHandler: this.setCurrentBulletContent,
+ continuous: { indefinite: false },
+ terminators: [completedCue, "bullet", "next"]
+ });
+ if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+ DictationManager.Controls.stop();
+ return;
+ }
+ this.nextBullet(this._editorView!.state.selection.to);
+ setTimeout(this.recordBullet, 2000);
+ }
+
+ setCurrentBulletContent = (value: string) => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let from = state.selection.from;
+ let to = state.selection.to;
+ this._editorView.dispatch(state.tr.insertText(value, from, to));
+ state = this._editorView.state;
+ let updated = TextSelection.create(state.doc, from, from + value.length);
+ this._editorView.dispatch(state.tr.setSelection(updated));
+ }
+ }
+
+ nextBullet = (pos: number) => {
+ if (this._editorView) {
+ let frag = Fragment.fromArray(this.newListItems(2));
+ let slice = new Slice(frag, 2, 2);
+ let state = this._editorView.state;
+ this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+ pos += 4;
+ state = this._editorView.state;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ }
+ }
+
+ private newListItems = (count: number) => {
+ let listItems: any[] = [];
+ for (let i = 0; i < count; i++) {
+ listItems.push(schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
+ }
+ return listItems;
+ }
+
componentDidMount() {
const config = {
schema,
@@ -287,19 +357,48 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}, { fireImmediately: true });
}
+ this.pullFromGoogleDoc(this.checkState);
+ this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
+
this._reactionDisposer = reaction(
() => {
const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
- return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+ return field ? field.Data : Blank;
},
- field2 => this._editorView && !this._applyingChange &&
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)))
+ incomingValue => {
+ if (this._editorView && !this._applyingChange) {
+ let updatedState = JSON.parse(incomingValue);
+ this._editorView.updateState(EditorState.fromJSON(config, updatedState));
+ this.tryUpdateHeight();
+ }
+ }
+ );
+
+ this.pullReactionDisposer = reaction(
+ () => this.props.Document[Pulls],
+ () => {
+ if (!DocumentDecorations.hasPulledHack) {
+ DocumentDecorations.hasPulledHack = true;
+ let unchanged = this.dataDoc.unchanged;
+ this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
+ }
+ }
);
- this.props.isOverlay && (this._heightReactionDisposer = reaction(
+ this.pushReactionDisposer = reaction(
+ () => this.props.Document[Pushes],
+ () => {
+ if (!DocumentDecorations.hasPushedHack) {
+ DocumentDecorations.hasPushedHack = true;
+ this.pushToGoogleDoc();
+ }
+ }
+ );
+
+ this._heightReactionDisposer = reaction(
() => this.props.Document[WidthSym](),
() => this.tryUpdateHeight()
- ));
+ );
this._textReactionDisposer = reaction(
() => this.extensionDoc,
@@ -328,8 +427,90 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this.unhighlightSearchTerms();
}
}, { fireImmediately: true });
+ setTimeout(() => this.tryUpdateHeight(), 0);
}
+ pushToGoogleDoc = async () => {
+ this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ let modes = GoogleApiClientUtils.WriteMode;
+ let mode = modes.Replace;
+ let reference: Opt<GoogleApiClientUtils.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ if (!reference) {
+ mode = modes.Insert;
+ reference = { service: GoogleApiClientUtils.Service.Documents, 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);
+ }
+ };
+ let undo = () => {
+ let content = exportState.body;
+ if (reference && content) {
+ GoogleApiClientUtils.Docs.write({ reference, content, mode });
+ }
+ };
+ UndoManager.AddEvent({ undo, redo });
+ redo();
+ });
+ }
+
+ pullFromGoogleDoc = async (handler: PullHandler) => {
+ let dataDoc = this.dataDoc;
+ let documentId = StrCast(dataDoc[GoogleRef]);
+ let exportState: GoogleApiClientUtils.ReadResult = {};
+ if (documentId) {
+ exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId });
+ }
+ UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+ }
+
+ updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ let pullSuccess = false;
+ if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+ const data = Cast(dataDoc.data, RichTextField);
+ if (data instanceof RichTextField) {
+ pullSuccess = true;
+ dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+ setTimeout(() => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let end = state.doc.content.size - 1;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+ }
+ }, 0);
+ dataDoc.title = exportState.title;
+ this.Document.customTitle = true;
+ dataDoc.unchanged = true;
+ }
+ } else {
+ delete dataDoc[GoogleRef];
+ }
+ DocumentDecorations.Instance.startPullOutcome(pullSuccess);
+ }
+
+ checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+ let data = Cast(dataDoc.data, RichTextField);
+ if (data) {
+ let storedPlainText = data[ToPlainText]() + "\n";
+ let receivedPlainText = exportState.body;
+ let storedTitle = dataDoc.title;
+ let receivedTitle = exportState.title;
+ let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle;
+ dataDoc.unchanged = unchanged;
+ DocumentDecorations.Instance.setPullState(unchanged);
+ }
+ }
+ }
+
+
clipboardTextSerializer = (slice: Slice): string => {
let text = "", separated = true;
const from = 0, to = slice.content.size;
@@ -448,7 +629,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this.props.selectOnLoad) {
if (!this.props.isOverlay) this.props.select(false);
else this._editorView!.focus();
- this.tryUpdateHeight();
}
}
@@ -457,6 +637,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
this._textReactionDisposer && this._textReactionDisposer();
+ this.pushReactionDisposer && this.pushReactionDisposer();
+ this.pullReactionDisposer && this.pullReactionDisposer();
this._heightReactionDisposer && this._heightReactionDisposer();
this._searchReactionDisposer && this._searchReactionDisposer();
}
@@ -533,6 +715,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@action
onFocused = (e: React.FocusEvent): void => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
+ document.addEventListener("keypress", this.recordKeyHandler);
+ this.tryUpdateHeight();
if (!this.props.isOverlay) {
FormattedTextBox.InputBoxOverlay = this;
} else {
@@ -582,6 +767,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
onBlur = (e: any) => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
if (this._undoTyping) {
this._undoTyping.end();
this._undoTyping = undefined;
@@ -610,13 +796,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@action
tryUpdateHeight() {
- if (this.props.Document.autoHeight && this.props.isOverlay) {
- let xf = this._ref.current!.getBoundingClientRect();
- let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
+ const ChromeHeight = this.props.ChromeHeight;
+ let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
+ if (this.props.Document.autoHeight && sh !== 0) {
let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
let dh = NumCast(this.props.Document.height, 0);
- let sh = scrBounds.height;
- const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight;
this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
this.dataDoc.nativeHeight = nh ? sh : undefined;
}
@@ -639,6 +823,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
// });
// ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
}
+
+
render() {
let self = this;
let style = this.props.isOverlay ? "scroll" : "hidden";
@@ -650,7 +836,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
<div className={`formattedTextBox-cont-${style}`} ref={this._ref}
style={{
overflowY: this.props.Document.autoHeight ? "hidden" : "auto",
- height: this.props.height ? this.props.height : undefined,
+ height: this.props.Document.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 708e00576..6fc94a140 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -89,10 +89,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
de.data.droppedDocuments.forEach(action((drop: Doc) => {
- if (de.mods === "CtrlKey") {
- Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc);
- e.stopPropagation();
- } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
+ if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
e.stopPropagation();
} else if (de.mods === "MetaKey") {
@@ -321,7 +318,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let rotation = NumCast(this.dataDoc.rotation) % 180;
let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
let aspect = realsize.height / realsize.width;
- if (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1) {
+ if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) {
setTimeout(action(() => {
layoutdoc.height = layoutdoc[WidthSym]() * aspect;
layoutdoc.nativeHeight = realsize.height;
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<FieldViewProps> {
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(<KeyValuePair doc={realDoc} ref={(function () {
let oldEl: KeyValuePair | undefined;
return (el: KeyValuePair) => {
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<KeyValuePairProps> {
PanelWidth: returnZero,
PanelHeight: returnZero,
addDocTab: returnZero,
+ pinToPres: returnZero,
ContentScaling: returnOne
};
let contents = <FieldView {...props} />;
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/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index a119eb39b..90b335933 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -52,7 +52,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
}
if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext);
}
else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6450cb826..18f82ff47 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -198,7 +198,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
<PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)}
Document={this.props.Document} DataDoc={this.props.DataDoc}
addDocTab={this.props.addDocTab} setPanY={this.setPanY}
- addDocument={this.props.addDocument}
+ pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
{this.settingsPanel()}
</div>);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
new file mode 100644
index 000000000..e376fbddb
--- /dev/null
+++ b/src/client/views/nodes/PresBox.tsx
@@ -0,0 +1,529 @@
+import React = require("react");
+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 { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+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 from "../presentationview/PresentationElement";
+import PresentationViewList from "../presentationview/PresentationList";
+import "../presentationview/PresentationView.scss";
+import { FieldView, FieldViewProps } from './FieldView';
+import { ContextMenu } from "../ContextMenu";
+
+library.add(faArrowLeft);
+library.add(faArrowRight);
+library.add(faPlay);
+library.add(faStop);
+library.add(faPlus);
+library.add(faTimes);
+library.add(faMinus);
+library.add(faEdit);
+
+
+export interface PresViewProps {
+ Documents: List<Doc>;
+}
+
+const expandedWidth = 450;
+
+@observer
+export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
+
+
+ public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+
+ 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 docs to their rendered component
+ @observable presElementsMappings: Map<Doc, PresentationElement> = 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;
+ //Mapping of guids to presentations.
+ @observable presentationsMapping: Map<String, Doc> = new Map();
+ //Mapping of presentations to guid, so that select option values can be given.
+ @observable presentationsKeyMapping: Map<Doc, String> = 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 opacity = 1;
+ @observable persistOpacity = true;
+ @observable labelOpacity = 0;
+ @observable presMode = false;
+
+ @observable public static CurrentPresentation: PresBox;
+
+ //initilize class variables
+ constructor(props: FieldViewProps) {
+ super(props);
+ runInAction(() => PresBox.CurrentPresentation = 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;
+ }
+ }
+
+ //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() {
+ this.setPresentationBackUps();
+ }
+
+
+ /**
+ * The function that retrieves the backUps for the current Presentation if present,
+ * otherwise initializes.
+ */
+ setPresentationBackUps = async () => {
+ //storing the presentation status,ie. whether it was stopped or playing
+ let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
+ runInAction(() => this.presStatus = presStatusBackUp);
+ }
+
+ //observable means render is re-called every time variable is changed
+ @observable
+ collapsed: boolean = false;
+ 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;
+ }
+ let nextSelected = current + 1;
+
+ let presDocs = DocListCast(this.curPresentation.data);
+ for (; nextSelected < presDocs.length - 1; nextSelected++) {
+ if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) {
+ break;
+ }
+ }
+
+ 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;
+ let zoomOut: boolean = false;
+
+ //checking if this presentation id is mapped to a group, if so chosing the first element in group
+ let presDocs = DocListCast(this.curPresentation.data);
+ let currentsArray: Doc[] = [];
+ for (; prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) {
+ currentsArray.push(presDocs[prevSelected]);
+ }
+ prevSelected = Math.max(0, prevSelected - 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)!.props.document.showButton) {
+ 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)!.showButton) {
+ 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) => {
+ //the order of cases is aligned based on priority
+ if (presElem.props.document.hideTillShownButton) {
+ if (this.childrenDocs.indexOf(key) <= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(key) < index) {
+ key.opacity = 0;
+ }
+ }
+ if (presElem.props.document.fadeButton) {
+ 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) => {
+ //the order of cases is aligned based on priority
+
+ if (presElem.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(key) >= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.fadeButton) {
+ if (this.childrenDocs.indexOf(key) >= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.hideTillShownButton) {
+ 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 willZoom: boolean = false;
+
+
+ let presDocs = DocListCast(this.curPresentation.data);
+ let nextSelected = presDocs.indexOf(curDoc);
+ let currentDocGroups: Doc[] = [];
+ for (; nextSelected < presDocs.length - 1; nextSelected++) {
+ if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) {
+ break;
+ }
+ currentDocGroups.push(presDocs[nextSelected]);
+ }
+
+ currentDocGroups.forEach((doc: Doc, index: number) => {
+ if (this.presElementsMappings.get(doc)!.navButton) {
+ docToJump = doc;
+ willZoom = false;
+ }
+ if (this.presElementsMappings.get(doc)!.showButton) {
+ 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
+ if (this.presElementsMappings.get(curDoc)!.navButton) {
+ DocumentManager.Instance.jumpToDocument(curDoc, false);
+ } else if (this.presElementsMappings.get(curDoc)!.showButton) {
+ let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(curDoc, true);
+ 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]);
+
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(docToJump, willZoom);
+ let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
+ curDoc.viewScale = newScale;
+ //saving the scale that user was on
+ if (curScale !== 1) {
+ this.childrenDocs[fromDoc].viewScale = curScale;
+ }
+
+ }
+
+ /**
+ * 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);
+
+ }
+ }
+
+ 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];
+ }
+ //this.RemoveDoc(indexOfDoc, true);
+ 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) => {
+ Doc.UnBrushAllDocs();
+ 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 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 <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>;
+ } else {
+ return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>;
+ }
+ }
+
+ //The function that starts or resets presentaton functionally, depending on status flag.
+ @action
+ startOrResetPres = () => {
+ if (this.presStatus) {
+ this.resetPresentation();
+ } else {
+ this.presStatus = true;
+ this.startPresentation(0);
+ const current = NumCast(this.curPresentation.selectedDoc);
+ this.gotoDocument(0, current);
+ }
+ this.curPresentation.presStatus = this.presStatus;
+ }
+
+ //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) => {
+ this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => {
+ if (component.props.document.hideTillShownButton) {
+ if (this.childrenDocs.indexOf(doc) > startIndex) {
+ doc.opacity = 0;
+ }
+
+ }
+ if (component.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(doc) < startIndex) {
+ doc.opacity = 0;
+ }
+ }
+ if (component.props.document.fadeButton) {
+ if (this.childrenDocs.indexOf(doc) < startIndex) {
+ doc.opacity = 0.5;
+ }
+ }
+
+ });
+
+ }
+
+
+ /**
+ * The function that is called to render either select for presentations, or title inputting.
+ */
+ renderSelectOrPresSelection = () => {
+ if (this.PresTitleInputOpen || this.PresTitleChangeOpen) {
+ return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * 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.PresTitleChangeOpen) {
+ this.PresTitleChangeOpen = false;
+ this.changePresentationTitle(presTitle);
+ }
+ }
+ }
+ /**
+ * 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;
+ }
+
+ addPressElem = (keyDoc: Doc, elem: PresentationElement) => {
+ this.presElementsMappings.set(keyDoc, elem);
+ }
+
+ minimize = undoBatch(action(() => {
+ this.presMode = true;
+ this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close");
+ }));
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+ }
+
+ render() {
+
+ let width = "100%"; //NumCast(this.curPresentation.width)
+ return (
+ <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu}
+ onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))}
+ style={{ width: width, opacity: this.opacity, }}>
+ <div className="presentation-buttons">
+ <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
+ {this.renderPlayPauseButton()}
+ <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ <button title="Minimize" className="presentation-button" onClick={this.minimize}><FontAwesomeIcon icon={"eye"} /></button>
+ </div>
+ <input
+ type="checkbox"
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ 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)}
+ />
+ <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
+ <PresentationViewList
+ mainDocument={this.curPresentation}
+ deleteDocument={this.RemoveDoc}
+ gotoDocument={this.gotoDocument}
+ PresElementsMappings={this.presElementsMappings}
+ setChildrenDocs={this.setChildrenDocs}
+ presStatus={this.presStatus}
+ removeDocByRef={this.removeDocByRef}
+ clearElemMap={() => this.presElementsMappings.clear()}
+ />
+ </div>
+ );
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 704030d85..3f4ee8960 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -34,7 +34,7 @@ library.add(faVideo);
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
- private _youtubePlayer: any = undefined;
+ private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
@@ -78,7 +78,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action public Pause = (update: boolean = true) => {
this.Playing = false;
update && this.player && this.player.pause();
- update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
+ update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
this._playTimer = undefined;
this.updateTimecode();
@@ -244,7 +244,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
if (started && event.data === YT.PlayerState.PLAYING) {
started = false;
- this._youtubePlayer.unMute();
+ this._youtubePlayer && this._youtubePlayer.unMute();
this.Pause();
return;
}
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<IAnnotationProps> {
@@ -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<IRegionAnnotationProps> {
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/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 7ca9d2d7d..0de1777e6 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -19,8 +19,8 @@ interface IPageProps {
numPages: number;
page: number;
pageLoaded: (page: Pdfjs.PDFPageViewport) => void;
- fieldExtensionDoc: Doc,
- Document: Doc,
+ fieldExtensionDoc: Doc;
+ Document: Doc;
renderAnnotations: (annotations: Doc[], removeOld: boolean) => void;
sendAnnotations: (annotations: HTMLDivElement[], page: number) => void;
createAnnotation: (div: HTMLDivElement, page: number) => void;
@@ -112,7 +112,7 @@ export default class Page extends React.Component<IPageProps> {
if (!BoolCast(annotationDoc.linkedToDoc)) {
let annotations = await DocListCastAsync(annotationDoc.annotations);
annotations && annotations.forEach(anno => anno.target = targetDoc);
- DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`)
+ DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`);
}
}
},
@@ -151,6 +151,9 @@ export default class Page extends React.Component<IPageProps> {
PDFMenu.Instance.fadeOut(true);
if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
+ if (!e.ctrlKey) {
+ this.props.sendAnnotations([], -1);
+ }
}
else {
// set marquee x and y positions to the spatially transformed position
@@ -161,14 +164,12 @@ export default class Page extends React.Component<IPageProps> {
}
this._marqueeing = true;
this._marquee.current && (this._marquee.current.style.opacity = "0.2");
+ this.props.sendAnnotations([], -1);
}
document.removeEventListener("pointermove", this.onSelectStart);
document.addEventListener("pointermove", this.onSelectStart);
document.removeEventListener("pointerup", this.onSelectEnd);
document.addEventListener("pointerup", this.onSelectEnd);
- if (!e.ctrlKey) {
- this.props.sendAnnotations([], -1);
- }
}
}
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index d98b66324..83413814f 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -1,21 +1,19 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
-import { faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch, faArrowRight } from '@fortawesome/free-solid-svg-icons';
+import { faArrowRight, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
+import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
import { Transform } from "../../util/Transform";
+import { ContextMenu } from "../ContextMenu";
import { DocumentView } from "../nodes/DocumentView";
-import { DocumentType } from "../../documents/Documents";
import React = require("react");
@@ -33,26 +31,9 @@ interface PresentationElementProps {
deleteDocument(index: number): void;
gotoDocument(index: number, fromDoc: number): Promise<void>;
allListElements: Doc[];
- groupMappings: Map<String, Doc[]>;
presStatus: boolean;
- presButtonBackUp: Doc;
- presGroupBackUp: Doc;
removeDocByRef(doc: Doc): boolean;
PresElementsMappings: Map<Doc, PresentationElement>;
-
-
-}
-
-//enum for the all kinds of buttons a doc in presentation can have
-export enum buttonIndex {
- Show = 0,
- Navigate = 1,
- HideTillPressed = 2,
- FadeAfter = 3,
- HideAfter = 4,
- Group = 5,
- OpenRight = 6
-
}
/**
@@ -62,37 +43,33 @@ export enum buttonIndex {
@observer
export default class PresentationElement extends React.Component<PresentationElementProps> {
- @observable private selectedButtons: boolean[];
private header?: HTMLDivElement | undefined;
private listdropDisposer?: DragManager.DragDropDisposer;
- private presElRef: React.RefObject<HTMLDivElement>;
- private backUpDoc: Doc | undefined;
-
-
- constructor(props: PresentationElementProps) {
- super(props);
- this.selectedButtons = new Array(7);
-
- this.presElRef = React.createRef();
- }
-
+ private presElRef: React.RefObject<HTMLDivElement> = React.createRef();
componentWillUnmount() {
this.listdropDisposer && this.listdropDisposer();
}
+ @computed get currentIndex() { return NumCast(this.props.mainDocument.selectedDoc); }
- /**
- * Getter to get the status of the buttons.
- */
- @computed
- get selected() {
- return this.selectedButtons;
- }
+ @computed get showButton() { return BoolCast(this.props.document.showButton); }
+ @computed get navButton() { return BoolCast(this.props.document.navButton); }
+ @computed get hideTillShownButton() { return BoolCast(this.props.document.hideTillShownButton); }
+ @computed get fadeButton() { return BoolCast(this.props.document.fadeButton); }
+ @computed get hideAfterButton() { return BoolCast(this.props.document.hideAfterButton); }
+ @computed get groupButton() { return BoolCast(this.props.document.groupButton); }
+ @computed get openRightButton() { return BoolCast(this.props.document.openRightButton); }
+ set showButton(val: boolean) { this.props.document.showButton = val; }
+ set navButton(val: boolean) { this.props.document.navButton = val; }
+ set hideTillShownButton(val: boolean) { this.props.document.hideTillShownButton = val; }
+ set fadeButton(val: boolean) { this.props.document.fadeButton = val; }
+ set hideAfterButton(val: boolean) { this.props.document.hideAfterButton = val; }
+ set groupButton(val: boolean) { this.props.document.groupButton = val; }
+ set openRightButton(val: boolean) { this.props.document.openRightButton = val; }
//Lifecycle function that makes sure that button BackUp is received when mounted.
async componentDidMount() {
- this.receiveButtonBackUp();
if (this.presElRef.current) {
this.header = this.presElRef.current;
this.createListDropTarget(this.presElRef.current);
@@ -107,156 +84,9 @@ export default class PresentationElement extends React.Component<PresentationEle
}
}
- /**
- * Function that will be called to receive stored backUp for buttons
- */
- receiveButtonBackUp = async () => {
-
- //get the list that stores docs that keep track of buttons
- let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
- if (!castedList) {
- this.props.presButtonBackUp.selectedButtonDocs = castedList = new List<Doc>();
- }
-
- let foundDoc: boolean = false;
-
- //if this is the first time this doc mounts, push a doc for it to store
-
- for (let doc of castedList) {
- let curDoc = await doc;
- let curDocId = StrCast(curDoc.docId);
- if (curDocId === this.props.document[Id]) {
- let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null);
- if (selectedButtonOfDoc !== undefined) {
- runInAction(() => this.selectedButtons = selectedButtonOfDoc);
- foundDoc = true;
- this.backUpDoc = curDoc;
- break;
- }
- }
- }
-
- if (!foundDoc) {
- let newDoc = new Doc();
- let defaultBooleanArray: boolean[] = new Array(7);
- newDoc.selectedButtons = new List(defaultBooleanArray);
- newDoc.docId = this.props.document[Id];
- castedList.push(newDoc);
- this.backUpDoc = newDoc;
- }
-
- }
-
- /**
- * The function that is called to group docs together. It tries to group a doc
- * that turned grouping option with the above document. If that doc is grouped with
- * other documents. Those other documents will be grouped with doc's above document as well.
- */
- @action
- onGroupClick = (document: Doc, index: number, buttonStatus: boolean) => {
- let p = this.props;
- if (index >= 1) {
- //checking if options was turned true
- if (buttonStatus) {
- //getting the id of the above-doc and the doc
- let aboveGuid = StrCast(p.allListElements[index - 1].presentId, null);
- let docGuid = StrCast(document.presentId, null);
- //the case where above-doc is already in group
- if (p.groupMappings.has(aboveGuid)) {
- let aboveArray = p.groupMappings.get(aboveGuid)!;
- //case where doc is already in group
- if (p.groupMappings.has(docGuid)) {
- let docsArray = p.groupMappings.get(docGuid)!;
- docsArray.forEach((doc: Doc) => {
- if (!aboveArray.includes(doc)) {
- aboveArray.push(doc);
- }
- doc.presentId = aboveGuid;
- });
- p.groupMappings.delete(docGuid);
- //the case where doc was not in group
- } else {
- if (!aboveArray.includes(document)) {
- aboveArray.push(document);
-
- }
-
- }
- //the case where above-doc was not in group
- } else {
- let newAboveArray: Doc[] = [];
- newAboveArray.push(p.allListElements[index - 1]);
-
- //the case where doc is in group
- if (p.groupMappings.has(docGuid)) {
- let docsArray = p.groupMappings.get(docGuid)!;
- docsArray.forEach((doc: Doc) => {
- newAboveArray.push(doc);
- doc.presentId = aboveGuid;
- });
- p.groupMappings.delete(docGuid);
-
- //the case where doc is not in a group
- } else {
- newAboveArray.push(document);
-
- }
- p.groupMappings.set(aboveGuid, newAboveArray);
-
- }
- document.presentId = aboveGuid;
-
- //when grouping is turned off
- } else {
- let curArray = p.groupMappings.get(StrCast(document.presentId, Utils.GenerateGuid()))!;
- let targetIndex = curArray.indexOf(document);
- let firstPart = curArray.slice(0, targetIndex);
- let firstPartNewGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid);
- let secondPart = curArray.slice(targetIndex);
- p.groupMappings.set(StrCast(p.allListElements[index - 1].presentId, Utils.GenerateGuid()), firstPart);
- p.groupMappings.set(StrCast(document.presentId, Utils.GenerateGuid()), secondPart);
-
-
- }
-
- }
- this.autoSaveGroupChanges();
-
- }
-
-
- /**
- * This function is called at the end of each group update to update the group updates.
- */
- @action
- autoSaveGroupChanges = () => {
- let castedList: List<Doc> = new List<Doc>();
- this.props.presGroupBackUp.groupDocs = castedList;
- this.props.groupMappings.forEach((docArray: Doc[], id: String) => {
- //create a new doc for each group
- let newGroupDoc = new Doc();
- castedList.push(newGroupDoc);
- //store the id of the group in the doc
- newGroupDoc.presentIdStore = id.toString();
- //store the doc array which represents the group in the doc
- newGroupDoc.grouping = new List(docArray);
- });
-
- }
-
- /**
- * Function that is called on click to change the group status of a docus, by turning the option on/off.
- */
@action
- changeGroupStatus = () => {
- if (this.selectedButtons[buttonIndex.Group]) {
- this.selectedButtons[buttonIndex.Group] = false;
- } else {
- this.selectedButtons[buttonIndex.Group] = true;
- }
- this.autoSaveButtonChange(buttonIndex.Group);
-
+ onGroupClick = (e: React.MouseEvent) => {
+ this.groupButton = !this.groupButton;
}
/**
@@ -266,31 +96,18 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onHideDocumentUntilPressClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.HideTillPressed]) {
- this.selectedButtons[buttonIndex.HideTillPressed] = false;
- if (this.props.index >= current) {
+ this.hideTillShownButton = !this.hideTillShownButton;
+ if (!this.hideTillShownButton) {
+ if (this.props.index >= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- this.selectedButtons[buttonIndex.HideTillPressed] = true;
if (this.props.presStatus) {
- if (this.props.index > current) {
+ if (this.props.index > this.currentIndex) {
this.props.document.opacity = 0;
}
}
}
- this.autoSaveButtonChange(buttonIndex.HideTillPressed);
- }
-
- /**
- * This function is called to get the updates for the changed buttons.
- */
- @action
- autoSaveButtonChange = async (index: buttonIndex) => {
- if (this.backUpDoc) {
- this.backUpDoc.selectedButtons = new List(this.selectedButtons);
- }
}
/**
@@ -301,25 +118,19 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.HideAfter]) {
- this.selectedButtons[buttonIndex.HideAfter] = false;
- if (this.props.index <= current) {
+ this.hideAfterButton = !this.hideAfterButton;
+ if (!this.hideAfterButton) {
+ if (this.props.index <= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- if (this.selectedButtons[buttonIndex.FadeAfter]) {
- this.selectedButtons[buttonIndex.FadeAfter] = false;
- }
- this.selectedButtons[buttonIndex.HideAfter] = true;
+ if (this.fadeButton) this.fadeButton = false;
if (this.props.presStatus) {
- if (this.props.index < current) {
+ if (this.props.index < this.currentIndex) {
this.props.document.opacity = 0;
}
}
}
- this.autoSaveButtonChange(buttonIndex.HideAfter);
-
}
/**
@@ -330,25 +141,19 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.FadeAfter]) {
- this.selectedButtons[buttonIndex.FadeAfter] = false;
- if (this.props.index <= current) {
+ this.fadeButton = !this.fadeButton;
+ if (!this.fadeButton) {
+ if (this.props.index <= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- if (this.selectedButtons[buttonIndex.HideAfter]) {
- this.selectedButtons[buttonIndex.HideAfter] = false;
- }
- this.selectedButtons[buttonIndex.FadeAfter] = true;
+ this.hideAfterButton = false;
if (this.props.presStatus) {
- if (this.props.index < current) {
+ if (this.props.index < this.currentIndex) {
this.props.document.opacity = 0.5;
}
}
}
- this.autoSaveButtonChange(buttonIndex.FadeAfter);
-
}
/**
@@ -357,22 +162,13 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onNavigateDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.Navigate]) {
- this.selectedButtons[buttonIndex.Navigate] = false;
-
- } else {
- if (this.selectedButtons[buttonIndex.Show]) {
- this.selectedButtons[buttonIndex.Show] = false;
- }
- this.selectedButtons[buttonIndex.Navigate] = true;
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (current === this.props.index) {
+ this.navButton = !this.navButton;
+ if (this.navButton) {
+ this.showButton = false;
+ if (this.currentIndex === this.props.index) {
this.props.gotoDocument(this.props.index, this.props.index);
}
}
-
- this.autoSaveButtonChange(buttonIndex.Navigate);
-
}
/**
@@ -381,23 +177,16 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onZoomDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.Show]) {
- this.selectedButtons[buttonIndex.Show] = false;
- this.props.document.viewScale = 1;
+ this.showButton = !this.showButton;
+ if (!this.showButton) {
+ this.props.document.viewScale = 1;
} else {
- if (this.selectedButtons[buttonIndex.Navigate]) {
- this.selectedButtons[buttonIndex.Navigate] = false;
- }
- this.selectedButtons[buttonIndex.Show] = true;
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (current === this.props.index) {
+ this.navButton = false;
+ if (this.currentIndex === this.props.index) {
this.props.gotoDocument(this.props.index, this.props.index);
}
}
-
- this.autoSaveButtonChange(buttonIndex.Show);
-
}
/**
@@ -407,13 +196,8 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onRightTabClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.OpenRight]) {
- this.selectedButtons[buttonIndex.OpenRight] = false;
- // action maybe
- } else {
- this.selectedButtons[buttonIndex.OpenRight] = true;
- }
- this.autoSaveButtonChange(buttonIndex.OpenRight);
+
+ this.openRightButton = !this.openRightButton;
}
/**
@@ -449,8 +233,6 @@ export default class PresentationElement extends React.Component<PresentationEle
//where does treeViewId come from
let movedDocs = (de.data.options === this.props.mainDocument[Id] ? de.data.draggedDocuments : de.data.droppedDocuments);
//console.log("How is this causing an issue");
- let droppedDoc: Doc = de.data.droppedDocuments[0];
- await this.updateGroupsOnDrop(droppedDoc, de);
document.removeEventListener("pointermove", this.onDragMove, true);
return (de.data.dropAction || de.data.userDropAction) ?
de.data.droppedDocuments.reduce((added: boolean, d: Doc) => Doc.AddDocToList(this.props.mainDocument, "data", d, this.props.document, before) || added, false)
@@ -463,221 +245,13 @@ export default class PresentationElement extends React.Component<PresentationEle
return false;
}
- /**
- * This method is called to update groups when the user drags and drops an
- * element to a different place. It follows the default behaviour and reconstructs
- * the groups in the way they would appear if clicked by user.
- */
- updateGroupsOnDrop = async (droppedDoc: Doc, de: DragManager.DropEvent) => {
-
- let x = this.ScreenToLocalListTransform(de.x, de.y);
- let rect = this.header!.getBoundingClientRect();
- let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2);
- let before = x[1] < bounds[1];
-
- let droppedDocIndex = this.props.allListElements.indexOf(droppedDoc);
-
- let dropIndexDiff = droppedDocIndex - this.props.index;
-
- //checking if the position it's dropped corresponds to current location with 3 cases.
- if (droppedDocIndex === this.props.index) {
- return;
- }
-
- if (dropIndexDiff === 1 && !before) {
- return;
- }
- if (dropIndexDiff === -1 && before) {
- return;
- }
-
- let p = this.props;
- let droppedDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(droppedDoc);
- let curDocGuid = StrCast(droppedDoc.presentId, null);
-
- //Splicing the doc from its current group, since it's moved
- if (p.groupMappings.has(curDocGuid)) {
- let groupArray = this.props.groupMappings.get(curDocGuid)!;
-
- if (droppedDocSelectedButtons[buttonIndex.Group]) {
- let groupIndexOfDrop = groupArray.indexOf(droppedDoc);
- let firstPart = groupArray.splice(0, groupIndexOfDrop);
-
- if (firstPart.length > 1) {
- let newGroupGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = newGroupGuid);
- this.props.groupMappings.set(newGroupGuid, firstPart);
- }
- }
-
- groupArray.splice(groupArray.indexOf(droppedDoc), 1);
- if (groupArray.length === 0) {
- this.props.groupMappings.delete(curDocGuid);
- }
- droppedDoc.presentId = Utils.GenerateGuid();
-
- //making sure to correct to groups after splicing, in case the dragged element
- //had the grouping on.
- let indexOfBelow = droppedDocIndex + 1;
- if (indexOfBelow < this.props.allListElements.length && indexOfBelow > 1) {
- let selectedButtonsOrigBelow: boolean[] = await this.getSelectedButtonsOfDoc(this.props.allListElements[indexOfBelow]);
- let aboveBelowDoc: Doc = this.props.allListElements[droppedDocIndex - 1];
- let aboveBelowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveBelowDoc);
- let belowDoc: Doc = this.props.allListElements[indexOfBelow];
- let belowDocPresId = StrCast(belowDoc.presentId);
-
- if (selectedButtonsOrigBelow[buttonIndex.Group]) {
- let belowDocGroup: Doc[] = this.props.groupMappings.get(belowDocPresId)!;
- if (aboveBelowDocSelectedButtons[buttonIndex.Group]) {
- let aboveBelowDocPresId = StrCast(aboveBelowDoc.presentId);
- if (this.props.groupMappings.has(aboveBelowDocPresId)) {
- let aboveBelowDocGroup: Doc[] = this.props.groupMappings.get(aboveBelowDocPresId)!;
- aboveBelowDocGroup.push(...belowDocGroup);
- this.props.groupMappings.delete(belowDocPresId);
- belowDocGroup.forEach((doc: Doc) => doc.presentId = aboveBelowDocPresId);
-
- }
- } else {
- belowDocGroup.unshift(aboveBelowDoc);
- aboveBelowDoc.presentId = belowDocPresId;
- }
-
-
- }
- }
-
- }
-
- //Case, when the dropped doc had the group button clicked.
- if (droppedDocSelectedButtons[buttonIndex.Group]) {
- if (before) {
- if (this.props.index > 0) {
- let aboveDoc = this.props.allListElements[this.props.index - 1];
- let aboveDocGuid = StrCast(aboveDoc.presentId);
- if (this.props.groupMappings.has(aboveDocGuid)) {
- this.protectOrderAndPush(aboveDocGuid, aboveDoc, droppedDoc);
- } else {
- this.createNewGroup(aboveDoc, droppedDoc, aboveDocGuid);
- }
- } else {
- let propsPresId = StrCast(this.props.document.presentId);
- if (this.selectedButtons[buttonIndex.Group]) {
- let propsArray = this.props.groupMappings.get(propsPresId)!;
- propsArray.unshift(droppedDoc);
- droppedDoc.presentId = propsPresId;
- }
- }
- } else {
- let propsDocGuid = StrCast(this.props.document.presentId);
- if (this.props.groupMappings.has(propsDocGuid)) {
- this.protectOrderAndPush(propsDocGuid, this.props.document, droppedDoc);
-
- } else {
- this.createNewGroup(this.props.document, droppedDoc, propsDocGuid);
- }
- }
-
-
- //if the group button of the element was not clicked.
- } else {
- if (before) {
- if (this.props.index > 0) {
-
- let aboveDoc = this.props.allListElements[this.props.index - 1];
- let aboveDocGuid = StrCast(aboveDoc.presentId);
- let aboveDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveDoc);
-
-
- if (this.selectedButtons[buttonIndex.Group]) {
- if (aboveDocSelectedButtons[buttonIndex.Group]) {
- let aboveGroupArray = this.props.groupMappings.get(aboveDocGuid)!;
- let propsDocPresId = StrCast(this.props.document.presentId);
-
- this.halveGroupArray(aboveDoc, aboveGroupArray, droppedDoc, propsDocPresId);
-
- } else {
- let belowPresentId = StrCast(this.props.document.presentId);
- let belowGroup = this.props.groupMappings.get(belowPresentId)!;
- belowGroup.splice(belowGroup.indexOf(aboveDoc), 1);
- belowGroup.unshift(droppedDoc);
- droppedDoc.presentId = belowPresentId;
- aboveDoc.presentId = Utils.GenerateGuid();
- }
-
-
- }
- } else {
- let propsPresId = StrCast(this.props.document.presentId);
- if (this.selectedButtons[buttonIndex.Group]) {
- let propsArray = this.props.groupMappings.get(propsPresId)!;
- propsArray.unshift(droppedDoc);
- droppedDoc.presentId = propsPresId;
- }
- }
- } else {
- if (this.props.index < this.props.allListElements.length - 1) {
- let belowDoc = this.props.allListElements[this.props.index + 1];
- let belowDocGuid = StrCast(belowDoc.presentId);
- let belowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(belowDoc);
-
- let propsDocGuid = StrCast(this.props.document.presentId);
-
- if (belowDocSelectedButtons[buttonIndex.Group]) {
- let belowGroupArray = this.props.groupMappings.get(belowDocGuid)!;
- if (this.selectedButtons[buttonIndex.Group]) {
-
- let propsGroupArray = this.props.groupMappings.get(propsDocGuid)!;
-
- this.halveGroupArray(this.props.document, propsGroupArray, droppedDoc, belowDocGuid);
-
- } else {
- belowGroupArray.splice(belowGroupArray.indexOf(this.props.document), 1);
- this.props.document.presentId = Utils.GenerateGuid();
- belowGroupArray.unshift(droppedDoc);
- droppedDoc.presentId = belowDocGuid;
- }
- }
-
- }
- }
- }
- this.autoSaveGroupChanges();
-
- }
-
- /**
- * This method returns the selectedButtons boolean array of the passed in doc,
- * retrieving it from the back-up.
- */
- getSelectedButtonsOfDoc = async (paramDoc: Doc) => {
- let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
- let foundSelectedButtons: boolean[] = new Array(7);
-
- //if this is the first time this doc mounts, push a doc for it to store
- for (let doc of castedList!) {
- let curDoc = await doc;
- let curDocId = StrCast(curDoc.docId);
- if (curDocId === paramDoc[Id]) {
- let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null);
- if (selectedButtonOfDoc !== undefined) {
- return selectedButtonOfDoc;
- }
- }
- }
-
- return foundSelectedButtons;
-
- }
-
//This is used to add dragging as an event.
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
- let selected = NumCast(this.props.mainDocument.selectedDoc, 0);
this.header!.className = "presentationView-item";
-
- if (selected === this.props.index) {
+ if (this.currentIndex === this.props.index) {
//this doc is selected
this.header!.className = "presentationView-item presentationView-selected";
}
@@ -687,13 +261,9 @@ export default class PresentationElement extends React.Component<PresentationEle
//This is used to remove the dragging when dropped.
onPointerLeave = (e: React.PointerEvent): void => {
- //to get currently selected presentation doc
- let selected = NumCast(this.props.mainDocument.selectedDoc, 0);
-
this.header!.className = "presentationView-item";
-
- if (selected === this.props.index) {
+ if (this.currentIndex === this.props.index) {
//this doc is selected
this.header!.className = "presentationView-item presentationView-selected";
@@ -729,62 +299,6 @@ export default class PresentationElement extends React.Component<PresentationEle
move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
return this.props.document !== target && this.props.removeDocByRef(doc) && addDoc(doc);
}
-
- /**
- * Helper method that gets called to divide a group array into two different groups
- * including the targetDoc in first part.
- * @param targetDoc document that is targeted as slicing point
- * @param propsGroupArray the array that gets divided into 2
- * @param droppedDoc the dropped document
- * @param belowDocGuid presentId of the belowGroup
- */
- private halveGroupArray(targetDoc: Doc, propsGroupArray: Doc[], droppedDoc: Doc, belowDocGuid: string) {
- let targetIndex = propsGroupArray.indexOf(targetDoc);
- let firstPart = propsGroupArray.slice(0, targetIndex + 1);
- let firstPartNewGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid);
- let secondPart = propsGroupArray.slice(targetIndex + 1);
- secondPart.unshift(droppedDoc);
- droppedDoc.presentId = belowDocGuid;
- this.props.groupMappings.set(firstPartNewGuid, firstPart);
- this.props.groupMappings.set(belowDocGuid, secondPart);
- }
-
- /**
- * Helper method that creates a new group, pushing above document first,
- * and dropped document second.
- * @param aboveDoc the document above dropped document
- * @param droppedDoc the dropped document itself
- * @param aboveDocGuid above document's presentId
- */
- private createNewGroup(aboveDoc: Doc, droppedDoc: Doc, aboveDocGuid: string) {
- let newGroup: Doc[] = [];
- newGroup.push(aboveDoc);
- newGroup.push(droppedDoc);
- droppedDoc.presentId = aboveDocGuid;
- this.props.groupMappings.set(aboveDocGuid, newGroup);
- }
-
- /**
- * Helper method that finds the above document's group, and pushes the
- * dropped document into that group, protecting the visual order of the
- * presentation elements.
- * @param aboveDoc the document above dropped document
- * @param droppedDoc the dropped document itself
- * @param aboveDocGuid above document's presentId
- */
- private protectOrderAndPush(aboveDocGuid: string, aboveDoc: Doc, droppedDoc: Doc) {
- let groupArray = this.props.groupMappings.get(aboveDocGuid)!;
- let tempStack: Doc[] = [];
- while (groupArray[groupArray.length - 1] !== aboveDoc) {
- tempStack.push(groupArray.pop()!);
- }
- groupArray.push(droppedDoc);
- droppedDoc.presentId = aboveDocGuid;
- while (tempStack.length !== 0) {
- groupArray.push(tempStack.pop()!);
- }
- }
/**
* This function is a getter to get if a document is in previewMode.
*/
@@ -839,6 +353,7 @@ export default class PresentationElement extends React.Component<PresentationEle
removeDocument={returnFalse}
ScreenToLocalTransform={Transform.Identity}
addDocTab={returnFalse}
+ pinToPres={returnFalse}
renderDepth={1}
PanelWidth={() => 350}
PanelHeight={() => 90}
@@ -871,11 +386,8 @@ export default class PresentationElement extends React.Component<PresentationEle
let p = this.props;
let title = p.document.title;
- //to get currently selected presentation doc
- let selected = NumCast(p.mainDocument.selectedDoc, 0);
-
let className = " presentationView-item";
- if (selected === p.index) {
+ if (this.currentIndex === p.index) {
//this doc is selected
className += " presentationView-selected";
}
@@ -891,23 +403,19 @@ export default class PresentationElement extends React.Component<PresentationEle
outlineStyle: "dashed",
outlineWidth: Doc.IsBrushed(p.document) ? `1px` : "0px",
}}
- onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
+ onClick={e => { p.gotoDocument(p.index, this.currentIndex); e.stopPropagation(); }}>
<strong className="presentationView-name">
{`${p.index + 1}. ${title}`}
</strong>
<button className="presentation-icon" onPointerDown={(e) => e.stopPropagation()} onClick={e => { this.props.deleteDocument(p.index); e.stopPropagation(); }}>X</button>
<br></br>
- <button title="Zoom" className={this.selectedButtons[buttonIndex.Show] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
- <button title="Navigate" className={this.selectedButtons[buttonIndex.Navigate] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
- <button title="Hide Document Till Presented" className={this.selectedButtons[buttonIndex.HideTillPressed] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
- <button title="Fade Document After Presented" className={this.selectedButtons[buttonIndex.FadeAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} color={"gray"} /></button>
- <button title="Hide Document After Presented" className={this.selectedButtons[buttonIndex.HideAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Group With Up" className={this.selectedButtons[buttonIndex.Group] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={(e) => {
- e.stopPropagation();
- this.changeGroupStatus();
- this.onGroupClick(p.document, p.index, this.selectedButtons[buttonIndex.Group]);
- }}> <FontAwesomeIcon icon={"arrow-up"} /> </button>
- <button title="Open Right" className={this.selectedButtons[buttonIndex.OpenRight] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onRightTabClick}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ <button title="Zoom" className={this.showButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
+ <button title="Navigate" className={this.navButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
+ <button title="Hide Document Till Presented" className={this.hideTillShownButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
+ <button title="Fade Document After Presented" className={this.fadeButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Hide Document After Presented" className={this.hideAfterButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Group With Up" className={this.groupButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onGroupClick}> <FontAwesomeIcon icon={"arrow-up"} /> </button>
+ <button title="Open Right" className={this.openRightButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onRightTabClick}><FontAwesomeIcon icon={"arrow-right"} /></button>
<br />
{this.renderEmbeddedInline()}
diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx
index e853c4070..930ce202e 100644
--- a/src/client/views/presentationview/PresentationList.tsx
+++ b/src/client/views/presentationview/PresentationList.tsx
@@ -1,27 +1,20 @@
-import { observer } from "mobx-react";
-import React = require("react");
import { action } from "mobx";
-import "./PresentationView.scss";
-import { Utils } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import PresentationElement, { buttonIndex } from "./PresentationElement";
-import "../../../new_fields/Doc";
-
-
+import { NumCast } from "../../../new_fields/Types";
+import PresentationElement from "./PresentationElement";
+import "./PresentationView.scss";
+import React = require("react");
interface PresListProps {
mainDocument: Doc;
deleteDocument(index: number): void;
gotoDocument(index: number, fromDoc: number): Promise<void>;
- groupMappings: Map<String, Doc[]>;
PresElementsMappings: Map<Doc, PresentationElement>;
setChildrenDocs: (docList: Doc[]) => void;
presStatus: boolean;
- presButtonBackUp: Doc;
- presGroupBackUp: Doc;
removeDocByRef(doc: Doc): boolean;
clearElemMap(): void;
@@ -34,35 +27,6 @@ interface PresListProps {
*/
export default class PresentationViewList extends React.Component<PresListProps> {
- /**
- * Method that initializes presentation ids for the
- * docs that is in the presentation, when presentation list
- * gets re-rendered. It makes sure to not assign ids to the
- * docs that are in the group, so that mapping won't be disrupted.
- */
-
- @action
- initializeGroupIds = async (docList: Doc[]) => {
- docList.forEach(async (doc: Doc, index: number) => {
- let docGuid = StrCast(doc.presentId, null);
- //checking if part of group
- let storedGuids: string[] = [];
- let castedGroupDocs = await DocListCastAsync(this.props.presGroupBackUp.groupDocs);
- //making sure the docs that were in groups, which were stored, to not get new guids.
- if (castedGroupDocs !== undefined) {
- castedGroupDocs.forEach((doc: Doc) => {
- let storedGuid = StrCast(doc.presentIdStore, null);
- if (storedGuid) {
- storedGuids.push(storedGuid);
- }
-
- });
- }
- if (!this.props.groupMappings.has(docGuid) && !storedGuids.includes(docGuid)) {
- doc.presentId = Utils.GenerateGuid();
- }
- });
- }
/**
* Initially every document starts with a viewScale 1, which means
@@ -80,7 +44,6 @@ export default class PresentationViewList extends React.Component<PresListProps>
render() {
const children = DocListCast(this.props.mainDocument.data);
- this.initializeGroupIds(children);
this.initializeScaleViews(children);
this.props.setChildrenDocs(children);
this.props.clearElemMap();
@@ -99,11 +62,8 @@ export default class PresentationViewList extends React.Component<PresListProps>
index={index}
deleteDocument={this.props.deleteDocument}
gotoDocument={this.props.gotoDocument}
- groupMappings={this.props.groupMappings}
allListElements={children}
presStatus={this.props.presStatus}
- presButtonBackUp={this.props.presButtonBackUp}
- presGroupBackUp={this.props.presGroupBackUp}
removeDocByRef={this.props.removeDocByRef}
PresElementsMappings={this.props.PresElementsMappings}
/>
diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss
index 65b09c833..5c40a8808 100644
--- a/src/client/views/presentationview/PresentationView.scss
+++ b/src/client/views/presentationview/PresentationView.scss
@@ -1,13 +1,14 @@
.presentationView-cont {
position: absolute;
- background: white;
z-index: 2;
box-shadow: #AAAAAA .2vw .2vw .4vw;
right: 0;
top: 0;
bottom: 0;
letter-spacing: 2px;
-
+ overflow: hidden;
+ transition: 0.7s opacity ease;
+ pointer-events: all;
}
.presentationView-item {
@@ -22,14 +23,11 @@
user-select: none;
transition: all .1s;
-
-
.documentView-node {
position: absolute;
z-index: 1;
}
-
}
.presentationView-item-above {
@@ -47,12 +45,15 @@
.presentationView-item:hover {
transition: all .1s;
- background: #AAAAAA
+ background: #AAAAAA;
+ border-radius: 12px;
}
.presentationView-selected {
background: gray;
color: black;
+ border-radius: 12px;
+ box-shadow: black 2px 2px 5px;
}
.presentationView-heading {
@@ -69,7 +70,6 @@
display: inline-block;
width: calc(100% - 200px);
letter-spacing: 2px;
-
}
.presentation-icon {
@@ -77,11 +77,12 @@
}
.presentation-interaction {
+ color: gray;
float: left;
}
.presentation-interaction-selected {
- background: #505050;
+ color: white;
float: left;
}
@@ -93,7 +94,8 @@
.presentation-button {
margin-right: 2.5%;
margin-left: 2.5%;
- width: 25%;
+ width: 20%;
+ border-radius: 5px;
}
.presentation-buttons {
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<Doc>;
-}
-
-const expandedWidth = 400;
-const presMinWidth = 300;
-
-@observer
-export class PresentationView extends React.Component<PresViewProps> {
- public static Instance: PresentationView;
-
- //Mapping from presentation ids to a list of doc that represent a group
- @observable groupMappings: Map<String, Doc[]> = new Map();
- //mapping from docs to their rendered component
- @observable presElementsMappings: Map<Doc, PresentationElement> = 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<String, Doc> = new Map();
- //Mapping of presentations to guid, so that select option values can be given.
- @observable presentationsKeyMapping: Map<Doc, String> = 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 <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>;
- } else {
- return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>;
- }
- }
-
- //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<HTMLSelectElement>) => {
- //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 <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
- } else {
- return <select value={this.currentSelectedPresValue} id="pres_selector" className="presentationView-title" onChange={this.getSelectedPresentation}>
- {presentationList.map((doc: Doc, index: number) => {
- let mappedGuid = this.presentationsKeyMapping.get(doc);
- let docGuid: string = mappedGuid ? mappedGuid.toString() : "";
- return <option key={docGuid} value={docGuid}>{StrCast(doc.title)}</option>;
- })}
- </select>;
- }
- }
-
- /**
- * 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 <PresModeMenu next={this.next} back={this.back} startOrResetPres={this.startOrResetPres} presStatus={this.presStatus} closePresMode={this.closePresMode} />;
- } else {
- return (null);
- }
-
- }
-
- render() {
-
- let width = NumCast(this.curPresentation.width);
-
- return (
- <div>
- <div className="presentationView-cont" onPointerEnter={action(() => !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" }}>
- <div className="presentationView-heading">
- {this.renderSelectOrPresSelection()}
- <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button>
- <button title="Open Presentation Mode" className="presentation-icon" style={{ marginRight: 10 }} onClick={this.openPresMode}><FontAwesomeIcon icon={"eye"} /></button>
- <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
- runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } });
- runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true);
- }}><FontAwesomeIcon icon={"plus"} /></button>
- <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button>
- <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
- runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } });
- runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true);
- }}><FontAwesomeIcon icon={"edit"} /></button>
- </div>
- <div className="presentation-buttons">
- <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
- {this.renderPlayPauseButton()}
- <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
- </div>
-
- <PresentationViewList
- mainDocument={this.curPresentation}
- deleteDocument={this.RemoveDoc}
- gotoDocument={this.gotoDocument}
- groupMappings={this.groupMappings}
- PresElementsMappings={this.presElementsMappings}
- setChildrenDocs={this.setChildrenDocs}
- presStatus={this.presStatus}
- presButtonBackUp={this.presButtonBackUp}
- presGroupBackUp={this.presGroupBackUp}
- removeDocByRef={this.removeDocByRef}
- clearElemMap={() => this.presElementsMappings.clear()}
- />
- <input
- type="checkbox"
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- 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)}
- />
- <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
- </div>
- <div className="mainView-libraryHandle"
- style={{ cursor: "ew-resize", right: `${width - 10}px`, backgroundColor: "white", opacity: this.opacity, transition: "0.7s opacity ease" }}
- onPointerDown={this.onPointerDown}>
- <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
- </div>
- {this.renderPresMode()}
-
- </div>
- );
- }
-}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 3e8582d61..c13d1d276 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -6,7 +6,7 @@ import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-s
import { library } from '@fortawesome/fontawesome-svg-core';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { DocumentType } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import { Cast, StrCast } from '../../../new_fields/Types';
import * as _ from "lodash";
import { IconBar } from './IconBar';
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 4712b0abc..c9924222f 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -4,7 +4,6 @@ import { observable, action } from 'mobx';
// import "./SearchBox.scss";
import "./IconBar.scss";
import "./IconButton.scss";
-import { DocumentType } from '../../documents/Documents';
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 5d23f6eeb..d2cfe7fad 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -6,7 +6,7 @@ import "./IconButton.scss";
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library, icon } from '@fortawesome/fontawesome-svg-core';
-import { DocumentType } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import '../globalCssVariables.scss';
import * as _ from "lodash";
import { IconBar } from './IconBar';
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 4dc409e77..2ad69daca 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -245,7 +245,6 @@ export class SearchBox extends React.Component {
@action.bound
closeSearch = () => {
- console.log("closing search")
FilterBox.Instance.closeFilter();
this.closeResults();
this._searchbarOpen = false;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 8201aa374..41fc49c2e 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -11,7 +11,7 @@ import { RichTextField } from "../../../new_fields/RichTextField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
@@ -203,6 +203,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
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 d634cf57f..f6114d476 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 { DocumentType } from "../client/documents/DocumentTypes";
+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 { DocumentType } from "../client/documents/Documents";
-import { ComputedField, ScriptField } from "./ScriptField";
+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<any> } = {};
-
+ public static CurrentUserEmail: string = "";
public async [HandleUpdate](diff: any) {
const set = diff.$set;
- const sameAuthor = this.author === 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<Doc>();
}
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;
@@ -445,20 +446,23 @@ export namespace Doc {
if (expandedTemplateLayout instanceof Doc) {
return expandedTemplateLayout;
}
+ if (expandedTemplateLayout instanceof Promise) {
+ return undefined;
+ }
let expandedLayoutFieldKey = "Layout[" + templateLayoutDoc[Id] + "]";
expandedTemplateLayout = dataDoc[expandedLayoutFieldKey];
if (expandedTemplateLayout instanceof Doc) {
return expandedTemplateLayout;
}
if (expandedTemplateLayout === undefined) {
- setTimeout(() =>
- dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"), 0);
+ setTimeout(() => dataDoc[expandedLayoutFieldKey] === undefined &&
+ (dataDoc[expandedLayoutFieldKey] = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]")), 0);
}
- return templateLayoutDoc; // use the templateLayout when it's not a template or the expandedTemplate is pending.
+ return undefined; // use the templateLayout when it's not a template or the expandedTemplate is pending.
}
export function GetLayoutDataDocPair(doc: Doc, dataDoc: Doc | undefined, fieldKey: string, childDocLayout: Doc) {
- let layoutDoc = childDocLayout;
+ let layoutDoc: Doc | undefined = childDocLayout;
let resolvedDataDoc = !doc.isTemplate && dataDoc !== doc && dataDoc ? Doc.GetDataDoc(dataDoc) : undefined;
if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
Doc.UpdateDocumentExtensionForField(resolvedDataDoc, fieldKey);
@@ -595,10 +599,14 @@ export namespace Doc {
});
}
- export class DocBrush {
+
+ export class DocData {
+ @observable _user_doc: Doc = undefined!;
@observable BrushedDoc: ObservableMap<Doc, boolean> = 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));
}
@@ -613,6 +621,10 @@ export namespace Doc {
manager.BrushedDoc.delete(doc);
manager.BrushedDoc.delete(Doc.GetDataDoc(doc));
}
+ export function UnBrushAllDocs() {
+ manager.BrushedDoc.clear();
+ }
}
Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(doc.title).replace(/\([0-9]*\)/, "") + `(${n})`; });
-Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); \ No newline at end of file
+Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
+Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); \ No newline at end of file
diff --git a/src/new_fields/ListSpec.ts b/src/new_fields/ListSpec.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/new_fields/ListSpec.ts
diff --git a/src/new_fields/PresField.ts b/src/new_fields/PresField.ts
new file mode 100644
index 000000000..f236a04fd
--- /dev/null
+++ b/src/new_fields/PresField.ts
@@ -0,0 +1,6 @@
+//insert code here
+import { ObjectField } from "./ObjectField";
+
+export abstract class PresField extends ObjectField {
+
+} \ No newline at end of file
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 89799b2af..1b52e6f82 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -4,6 +4,11 @@ import { Deserializable } from "../client/util/SerializationHelper";
import { Copy, ToScriptString } from "./FieldSymbols";
import { scriptingGlobal } from "../client/util/Scripting";
+export const ToPlainText = Symbol("PlainText");
+export const FromPlainText = Symbol("PlainText");
+const delimiter = "\n";
+const joiner = "";
+
@scriptingGlobal
@Deserializable("RichTextField")
export class RichTextField extends ObjectField {
@@ -22,4 +27,49 @@ export class RichTextField extends ObjectField {
[ToScriptString]() {
return `new RichTextField("${this.Data}")`;
}
+
+ public static Initialize = (initial: string) => {
+ !initial.length && (initial = " ");
+ let pos = initial.length + 1;
+ return `{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"${initial}"}]}]},"selection":{"type":"text","anchor":${pos},"head":${pos}}}`;
+ }
+
+ [ToPlainText]() {
+ // Because we're working with plain text, just concatenate all paragraphs
+ let content = JSON.parse(this.Data).doc.content;
+ let paragraphs = content.filter((item: any) => item.type === "paragraph");
+
+ // Functions to flatten ProseMirror paragraph objects (and their components) to plain text
+ // While this function already exists in state.doc.textBeteen(), it doesn't account for newlines
+ let blockText = (block: any) => block.text;
+ let concatenateParagraph = (p: any) => (p.content ? p.content.map(blockText).join(joiner) : "") + delimiter;
+
+ // Concatentate paragraphs and string the result together
+ let textParagraphs: string[] = paragraphs.map(concatenateParagraph);
+ let plainText = textParagraphs.join(joiner);
+ return plainText.substring(0, plainText.length - 1);
+ }
+
+ [FromPlainText](plainText: string) {
+ // Remap the text, creating blocks split on newlines
+ let elements = plainText.split(delimiter);
+
+ // Google Docs adds in an extra carriage return automatically, so this counteracts it
+ !elements[elements.length - 1].length && elements.pop();
+
+ // Preserve the current state, but re-write the content to be the blocks
+ let parsed = JSON.parse(this.Data);
+ parsed.doc.content = elements.map(text => {
+ let paragraph: any = { type: "paragraph" };
+ text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break
+ return paragraph;
+ });
+
+ // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it
+ parsed.selection = { type: "text", anchor: 1, head: 1 };
+
+ // Export the ProseMirror-compatible state object we've jsut built
+ return JSON.stringify(parsed);
+ }
+
} \ No newline at end of file
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/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
new file mode 100644
index 000000000..f71886d8c
--- /dev/null
+++ b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
Binary files differ
diff --git a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
new file mode 100644
index 000000000..30ddb3091
--- /dev/null
+++ b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_NewO.docx b/src/scraping/buxton/source/Bill_Notes_NewO.docx
deleted file mode 100644
index a514926d2..000000000
--- a/src/scraping/buxton/source/Bill_Notes_NewO.docx
+++ /dev/null
Binary files differ
diff --git a/src/scraping/buxton/source/Bill_Notes_OLPC.docx b/src/scraping/buxton/source/Bill_Notes_OLPC.docx
deleted file mode 100644
index 7a636e2d6..000000000
--- a/src/scraping/buxton/source/Bill_Notes_OLPC.docx
+++ /dev/null
Binary files differ
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<T> {
private _name: string;
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index e30015e39..014906054 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..8785cd974
--- /dev/null
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -0,0 +1,130 @@
+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.
+ */
+export namespace GoogleApiServerUtils {
+
+ // If modifying these scopes, delete token.json.
+ const prefix = 'https://www.googleapis.com/auth/';
+ const SCOPES = [
+ 'documents.readonly',
+ 'documents',
+ 'presentations',
+ 'presentations.readonly',
+ 'drive',
+ 'drive.file',
+ ];
+
+ export const parseBuffer = (data: Buffer) => JSON.parse(data.toString());
+
+ export enum Service {
+ Documents = "Documents",
+ Slides = "Slides"
+ }
+
+
+ export interface CredentialPaths {
+ credentials: string;
+ token: string;
+ }
+
+ export type ApiResponse = Promise<GaxiosResponse>;
+ export type ApiRouter = (endpoint: Endpoint, paramters: any) => ApiResponse;
+ export type ApiHandler = (parameters: any) => ApiResponse;
+ export type Action = "create" | "retrieve" | "update";
+
+ 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<Opt<Endpoint>>((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<Endpoint>;
+ let parameters: EndpointParameters = { auth, version: "v1" };
+ switch (sector) {
+ case Service.Documents:
+ routed = google.docs(parameters).documents;
+ break;
+ case Service.Slides:
+ routed = google.slides(parameters).presentations;
+ break;
+ }
+ resolve(routed);
+ });
+ });
+ });
+ };
+
+
+ /**
+ * 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<OAuth2Client> {
+ 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<OAuth2Client>((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<OAuth2Client>((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/youtubeApi/youtubeApiSample.d.ts b/src/server/apis/youtube/youtubeApiSample.d.ts
index 427f54608..427f54608 100644
--- a/src/server/youtubeApi/youtubeApiSample.d.ts
+++ b/src/server/apis/youtube/youtubeApiSample.d.ts
diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/apis/youtube/youtubeApiSample.js
index 50b3c7b38..50b3c7b38 100644
--- a/src/server/youtubeApi/youtubeApiSample.js
+++ b/src/server/apis/youtube/youtubeApiSample.js
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index f36f5b73d..f7ce24967 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, PromiseValue } 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>();
doc.gridGap = 5;
@@ -52,12 +49,20 @@ 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<Doc>(), { 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;
@@ -70,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() {
@@ -85,15 +91,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 {
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..34a0a19f1 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,20 @@ 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';
+var AdmZip = require('adm-zip');
+import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
import { Response } from 'express-serve-static-core';
+import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
+import { GaxiosResponse } from 'gaxios';
+import { Opt } from '../new_fields/Doc';
+import { docs_v1 } from 'googleapis';
+import { Endpoint } from 'googleapis-common';
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;
@@ -174,6 +174,13 @@ const read_text_file = (relativePath: string) => {
});
};
+const write_text_file = (relativePath: string, contents: any) => {
+ let target = path.join(__dirname, relativePath);
+ return new Promise<void>((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) {
@@ -352,7 +359,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);
@@ -361,13 +368,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;
@@ -790,6 +801,32 @@ 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");
+
+const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
+ ["create", (api, params) => api.create(params)],
+ ["retrieve", (api, params) => api.get(params)],
+ ["update", (api, params) => api.batchUpdate(params)],
+]);
+
+app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => {
+ let sector = req.params.sector;
+ let action = req.params.action;
+ GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[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)
+ );
+ execute.catch(exception => res.send(exception));
+ return;
+ }
+ res.send(undefined);
+ });
+});
+
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
"number": "_n",
"string": "_t",
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