diff --git a/google-realtime/google-realtime.d.ts b/google-realtime/google-realtime.d.ts new file mode 100644 index 0000000000..b72471a045 --- /dev/null +++ b/google-realtime/google-realtime.d.ts @@ -0,0 +1,521 @@ +// Type definitions for Google Realtime API +// Project: https://developers.google.com/google-apps/realtime/ +// Definitions by: Dustin Wehr +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// For Typescript newbs: To get shorter names, use e.g. +// type CollabModel = googleRealtime.Model; +// interface CollabList extends googleRealtime.CollaborativeList {} +// See section "Type Aliases" of http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf + +// Note the occurrences of "INCOMPLETE". For some interfaces and object types, I have only included +// the properties and methods that I've actually used so-far, and will add more as they become useful to me. +// Or, maybe you want to complete them? + +declare module googleRealtime { + + type GoogEventHandler = ((evt:ObjectChangedEvent) => void) | ((e:Event) => void) | EventListener; + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeObject + export class CollaborativeObject { + // The id of this collaborative object. Read-only. + id:string; + + // The type of this collaborative object. For standard collaborative objects, + // see gapi.drive.realtime.CollaborrativeType for possible values; for custom collaborative objects, this value is + // application-defined. + // Addition: the possible values for standard objects are EditableString, List, and Map. + type:string; + + // Adds an event listener to the event target. The same handler can only be added once per the type. + // Even if you add the same handler multiple times using the same type then it will only be called once + // when the event is dispatched. + addEventListener(type:string, listener: GoogEventHandler, opt_capture?:boolean):void; + + // Removes all event listeners from this object. + removeAllEventListeners():void; + + // Removes an event listener from the event target. The handler must be the same object as the one added. + // If the handler has not been added then nothing is done. + removeEventListener(type:string, listener: GoogEventHandler, opt_capture?:boolean):void; + + // Returns a string representation of this collaborative object. + toString():string; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.IndexReference + export class IndexReference extends CollaborativeObject { + // (Categories of) the shift behavior of an index reference when the element it points at is deleted. + static DeleteMode:{ + SHIFT_AFTER_DELETE: string + SHIFT_BEFORE_DELETE: string + SHIFT_TO_INVALID: string + }; + + //The index of the current location the reference points to. Write to this property to change the referenced index. + index:number; + + // The behavior of this index reference when the element it points at is deleted. + // @return one of the elements of DeleteMode + deleteMode():string; + + // The object this reference points to. Read-only. + referencedObject():V; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeMap + export class CollaborativeMap extends CollaborativeObject { + size:string; + + static type:string; // equals "Map" + + // Removes all entries. + clear():void; + + // Removes the entry for the given key (if such an entry exists). + // @return the value that was mapped to this key, or null if there was no existing value. + delete(key:string):V; + + // Returns the value mapped to the given key. + get(key:string):V; + + // Checks if this map contains an entry for the given key. + has(key:string):boolean; + + // Returns whether this map is empty. + isEmpty():boolean; + + // Returns an array containing a copy of the items in this map. Modifications to the returned array do + // not modify this collaborative map. + // @return non-null Array of Arrays, where the inner arrays are tupples [string, V] + items():[string,V][]; + + // Returns an array containing a copy of the keys in this map. Modifications to the returned array + // do not modify this collaborative map. + keys():string[]; + + // Put the value into the map with the given key, overwriting an existing value for that key. + // @return the old map value, if any, that used to be mapped to the given key. + set(key:string, value:V):V; + + // Returns an array containing a copy of the values in this map. Modifications to the returned array + // do not modify this collaborative map. + values():V[]; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeString + export class CollaborativeString extends CollaborativeObject { + // The length of the string. Read only. + length:number; + + // The text of this collaborative string. Reading from this property is equivalent to calling getText(). Writing to this property is equivalent to calling setText(). + text:string; + + static type:string; // equals "EditableString" + + // Appends a string to the end of this one. + append(text:string):void; + + // Gets a string representation of the collaborative string. + getText():string; + + // Inserts a string into the collaborative string at a specific index. + insertString(index:number, text:string):void; + + // Creates an IndexReference at the given {@code index}. If {@code canBeDeleted} is set, then a delete + // over the index will delete the reference. Otherwise the reference will shift to the beginning of the deleted range. + registerReference(index:number, canBeDeleted:boolean):IndexReference; + + // Deletes the text between startIndex (inclusive) and endIndex (exclusive). + removeRange(startIndex:number, endIndex:number):void; + + // Sets the contents of this collaborative string. Note that this method performs a text diff between the + // current string contents and the new contents so that the string will be modified using the minimum number + // of text inserts and deletes possible to change the current contents to the newly-specified contents. + setText(text:string):void; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeList + export class CollaborativeList extends CollaborativeObject { + // The number of entries in the list. Assign to this field to reduce the size of the list. + // Note that the length given must be less than or equal to the current size. + // The length of a list cannot be extended in this way. + length:number; + + static type:string; // equals "List" + + // Returns a copy of the contents of this collaborative list as an array. + // Changes to the returned object will not affect the original collaborative list. + asArray():V[]; + + // Removes all values from the list. + clear():void; + + // Gets the value at the given index. + get(ind:number):V; + + //Returns the first index of the given value, or -1 if it cannot be found. + indexOf(value:V, opt_comparatorFn?:(x1:V, x2:V) => boolean):number; + + //Inserts an item into the list at a given index. + insert(index:number, value:V):void; + + // Inserts a list of items into the list at a given index. + insertAll(index:number, values:V[]):void; + + // Returns the last index of the given value, or -1 if it cannot be found. + lastIndexOf(value:V, opt_comparatorFn?:(x1:V, x2:V) => boolean):number; + + //Moves a single element in this list (at index) to immediately before destinationIndex. + //Both indices are with respect to the position of elements before the move. + //For example, given the list: ['A', 'B', 'C'] + //move(0, 0) is a no-op + //move(0, 1) is a no-op + //move(0, 2) yields ['B', 'A', 'C'] ('A' is moved to immediately before 'C') + //move(0, 3) yields ['B', 'C', 'A'] ('A' is moved to immediately before an imaginary element after the list end) + //move(1, 0) yields ['B', 'A', 'C'] ('B' is moved to immediately before 'A') + //move(1, 1) is a no-op + //move(1, 2) is a no-op + //move(1, 3) yields ['A', 'C', 'B'] ('B' is moved to immediately before an imaginary element after the list end) + move(index:number, destinationIndex:number):void; + + // Moves a single element in this list (at index) to immediately before destinationIndex in the list destination. + // Both indices are with respect to the position of elements before the move. + // If the provided destination is this list, this function is identical to move(index, destinationIndex). + moveToList(index:number, destination:CollaborativeList, destinationIndex:number):void; + + // Adds an item to the end of the list. + // @return the new length of the list + push(value:V):number; + + // Adds an array of values to the end of the list. + pushAll(values:V[]):void; + + // Creates an IndexReference at the given index. If canBeDeleted is true, then a delete over the index will delete + // the reference. Otherwise the reference will shift to the beginning of the deleted range. + registerReference(index:number, canBeDeleted:boolean):IndexReference>; + + // Removes the item at the given index from the list. + remove(index:number):void; + + // Removes the items between startIndex (inclusive) and endIndex (exclusive). + removeRange(startIndex:number, endIndex:number):void; + + // Removes the first instance of the given value from the list. + // @return whether the item was removed + removeValue(value:V):boolean; + + // Replaces items in the list with the given items, starting at the given index. + replaceRange(index:number, values:V[]):void; + + // Sets the item at the given index + set(index:number, value:V):void; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.Model + export class Model { + + // Returns the collaborative object with the given id. + // @return non-null Object + getObject:any; + + // An estimate of the number of bytes used by data stored in the model. + bytesUsed:number; + + // True if the model can currently redo. + canRedo:boolean; + + // True if the model can currently undo. + canUndo:boolean; + + // Creates the native JS object for a given collaborative object type. + // @return non-null Object + createJsObject(typeName:string):any; + + // Adds an event listener to the event target. + // The same handler can only be added once per the type. Even if you add the same handler multiple times using the + // same type then it will only be called once when the event is dispatched. + addEventListener(type:string, listener:() => void | EventListener, opt_capture?:boolean):void; + + // Starts a compound operation. If a name is given, that name will be recorded in the mutation for use in revision + // history, undo menus, etc. When beginCompoundOperation() is called, all subsequent edits to the data model will + // be batched together in the undo stack and revision history until endCompoundOperation() is called. + // Compound operations may be nested inside other compound operations. + // If the root compound operation is undoable, all nested compound operations must be undoable as well. + // If the root compound operation is non-undoable, nested operations can be undoable, although the entire operation + // will obey the root's opt_isUndoable value. + // Note that the compound operation MUST start and end in the same synchronous execution block. If this invariant + // is violated, the data model will become invalid and all future changes will fail. + beginCompoundOperation(opt_name?:string, opt_isUndoable?:boolean):void; + + + // Creates and returns a new collaborative object. This can be used to create custom collaborative objects. + // For built in types, use the specific create* functions. + // @return non-null Object + create(ref:string|Function, ...var_args:any[]):any; + + // Creates a collaborative list. + createList(opt_initialValue?:Array):CollaborativeList; + + // Creates a collaborative map. + createMap(opt_initialValue?:Array<[string,T]>):CollaborativeMap; + + // Creates a collaborative string. + createString(opt_initialValue?:string):CollaborativeString; + + //Ends a compound operation. This method will throw an exception if no compound operation is in progress. + endCompoundOperation():void; + + // Returns the root of the object model. + getRoot():CollaborativeMap; + + // The mode of the document. If true, the document is read-only. If false, it is editable. + isReadOnly():boolean; + + // Redo the last thing the active collaborator undid. + redo():void; + + // Removes all event listeners from this object. + removeAllEventListeners():void; + + // Removes an event listener from the event target. The handler must be the same object as the one added. + // If the handler has not been added then nothing is done. + removeEventListener(type:string, listener:() => void | EventListener, opt_capture?:boolean):void; + + // The current server revision number for this model. The revision number begins at 1 (the initial empty model) + // and is incremented each time the model is changed on the server (either by the current session or any + // other collaborator). Because this revision number includes only changes that the server knows about, + // it is only updated while this client is connected to the Realtime API server and it does not include changes + // that have not yet been saved to the server. + serverRevision():number; + + // Serializes this data model to a JSON-based format which is compatible with the Realtime API's import/export + // REST API. The exported JSON can also be used with gapi.drive.realtime.loadFromJson to load an in-memory + // version of this data model which does not require a network connection. + // See https://developers.google.com/drive/v2/reference/realtime/update for more information. + toJson(opt_appId?:string, opt_revision?:number):string; + + // Undo the last thing the active collaborator did. + undo():void; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.BaseModelEvent + interface BaseModelEvent { + // Whether this event bubbles. + bubbles : boolean; + + // The list of names from the hierarchy of compound operations that initiated this event. + compoundOperationNames : string[]; + + // True if this event originated in the local session. + isLocal : boolean; + + // True if this event originated from a redo call. + isRedo : boolean; + + // True if this event originated from an undo call. + isUndo : boolean; + + // Prevents an event from performing its default action. In the Realtime API, this function is only present + // for compatibility with the DOM event interface and therefore it does nothing. + preventDefault() : void; + + // The id of the session that initiated this event. + sessionId : string; + + // The collaborative object that initiated this event. + target : Object; + + // The type of the event. + type : string; + + // The user id of the user that initiated this event. + userId : string; + + // Stops an event which bubbles from propagating to the target's parent. + stopPropagation() : void; + + /* Parameters: + target + gapi.drive.realtime.CollaborativeObject + The collaborative object that initiated the event. + Value must not be null. + + sessionId + string + The id of the session that initiated the event. + + userId + string + The user id of the user that initiated the event. + + compoundOperationNames + Array of string + The list of names from the hierarchy of compound operations that initiated the event. + Value must not be null. + isLocal + boolean + True if the event originated in the local session. + + isUndo + boolean + True if the event originated from an undo call. + + isRedo + boolean + True if the event originated from a redo call. + */ + new (target:CollaborativeObject, sessionId:string, userId:string, compoundOperationNames: string[], + isLocal:boolean, isUndo:boolean, isRedo:boolean) : BaseModelEvent; + } + + // Complete + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.ObjectChangedEvent + interface ObjectChangedEvent extends BaseModelEvent { + // parameters as in BaseModelEvent above except for addition of: + // events: + // Array of gapi.drive.realtime.BaseModelEvent + // The specific events that document the changes that occurred on the object. + // Value must not be null. + new (target:CollaborativeObject, sessionId:string, userId:string, compoundOperationNames: string[], + isLocal:boolean, isUndo:boolean, isRedo:boolean, events:BaseModelEvent[]) : ObjectChangedEvent; + + // The specific events that document the changes that occurred on the object. + events : BaseModelEvent[]; + } + + + // INCOMPLETE + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.Document + export class Document { + // Gets the collaborative model associated with this document. + // @return non-null Model + getModel():Model; + + // Closes the document and disconnects from the server. + // After this function is called, event listeners will no longer fire and attempts to access the document, model, + // or model objects will throw a gapi.drive.realtime.DocumentClosedError. + // Calling this function after the document has been closed will have no effect. + close():void; + } + + // *********************************** + // The remainder of this file types some (not all) things in realtime-client-utils.js, found here: + // https://developers.google.com/google-apps/realtime/realtime-quickstart + // and + // https://apis.google.com/js/api.js + // *********************************** + + + // Complete + export interface LoaderOptions { + // Your Application ID from the Google APIs Console. + appId: string; + + // Autocreate files right after auth automatically. + autoCreate: boolean; + + // Client ID from the console. + clientId: string; + + // The ID of the button to click to authorize. Must be a DOM element ID. + authButtonElementId: string; + + // The MIME type of newly created Drive Files. By default the application + // specific MIME type will be used: + // application/vnd.google-apps.drive-sdk. + newFileMimeType: string; + //newFileMimeType = null // default + + // Function to be called to initialize custom Collaborative Objects types. + registerTypes: () => void; + + // The name of newly created Drive files, if no title is specified. + defaultTitle: string; + + // Function to be called after authorization and before loading files. + afterAuth: () => void; + + // Function to be called when a Realtime model is first created. + initializeModel: (model:Model) => void; + + // Function to be called every time a Realtime file is loaded. + onFileLoaded: (rtdoc:Document) => void; + } + + // INCOMPLETE + export interface DriveAPIFileResource { + id: string; + } + + // INCOMPLETE + export interface RealtimeLoader { + start():void; + load():void; + } + interface RealtimeLoaderFactory { + new (options:googleRealtime.LoaderOptions) : RealtimeLoader; + } + + // INCOMPLETE + export interface ClientUtils { + // INCOMPLETE + params: { + // string containing one or more file ids separated by spaces. + fileIds : string + }; + RealtimeLoader : RealtimeLoaderFactory; + + /** + * Creates a new Realtime file. + * @param title {string} title of the newly created file. + * @param mimeType {string} the MIME type of the new file. + * @param callback {(file:DriveAPIFileResource) => void} the callback to call after creation. + */ + createRealtimeFile(title:string, mimeType:string, callback:(file:DriveAPIFileResource) => void) : void; + } + + // COMPLETE + // https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.databinding.Binding + export interface Binding { + // Throws gapi.drive.realtime.databinding.AlreadyBoundError If domElement has already been bound. + + // The collaborative object to bind. + collaborativeObject : CollaborativeObject; + + // The DOM element that the collaborative object is bound to. Value must not be null. + domElement : Element; + + // Unbinds the domElement from collaborativeObject. + unbind() : void; + } + + export interface GoogleAPI { + drive : { + realtime : { + databinding : { + bindString(s:googleRealtime.CollaborativeString, textinput:HTMLInputElement) : googleRealtime.Binding; + } + EventType : { + TEXT_INSERTED: string; + TEXT_DELETED: string; + OBJECT_CHANGED: string; + } + } + } + } + +} + +// global var introduced by realtime-client-utils.js +declare var rtclient:googleRealtime.ClientUtils; + +// global var introduced by https://apis.google.com/js/api.js +declare var gapi: googleRealtime.GoogleAPI; \ No newline at end of file diff --git a/google-realtime/library-tests.ts b/google-realtime/library-tests.ts new file mode 100644 index 0000000000..5adc8184af --- /dev/null +++ b/google-realtime/library-tests.ts @@ -0,0 +1,196 @@ +/// + +// Don't use this as a reference. Use the examples at +// https://developers.google.com/google-apps/realtime/ +// To use the Realtime API effectively, I needed to read lots of the +// (well-written) documentation on the site, and to understand parts of +// realtime-client-utils.js +// which you can find in the tutorial section of the project's homepage. + +declare var $ : any; +interface JQuery { + [key: string]: any; +}; + +type CollabModel = googleRealtime.Model; +type CollabDoc = googleRealtime.Document; +interface CollaborativeObject extends googleRealtime.CollaborativeObject {} +interface CollaborativeList extends googleRealtime.CollaborativeList {} +interface CollaborativeMap extends googleRealtime.CollaborativeMap {} +interface IndexReference extends googleRealtime.IndexReference {} +interface CollaborativeString extends googleRealtime.CollaborativeString {} + +type CListOfCObj = CollaborativeList +type CObjOrStr = CollaborativeObject | string; +type CMapOfCObjOrStr = CollaborativeMap; + + +module GRealtime { + + + + + var default_loader_options : googleRealtime.LoaderOptions = { + // Your Application ID from the Google APIs Console. + appId: "YOUR_APP_ID", + + // This tells us if need to we automatically create a file after auth. + autoCreate: false, + + // Client ID from the console. + clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com', + + // The ID of the button to click to authorize. Must be a DOM element ID. + authButtonElementId: 'realtime-authorize-button', + + // The MIME type of newly created Drive Files. By default the application + // specific MIME type will be used: + // application/vnd.google-apps.drive-sdk. + //newFileMimeType: 'text/json', + newFileMimeType: 'text', + //newFileMimeType: null, // default + + // Function to be called to initialize custom Collaborative Objects types. + registerTypes: null, // No action + + defaultTitle: "Default default-doc-title", + + // The rest are only defaults + afterAuth: function() : void { + console.log("default afterAuth called") + }, + + initializeModel: function(rtmodel:CollabModel) : void { + console.log("default initializeModel called"); + }, + + onFileLoaded : function(rtdoc:CollabDoc) : void { + console.log("default onFileLoaded called"); + } + + }; + + export class MyRTLoader { + public loader_options : googleRealtime.LoaderOptions = $.extend({},default_loader_options); + private rtloader_client : googleRealtime.RealtimeLoader; + + // call after setting loader_options appropriately + authorize() { + this.rtloader_client = new rtclient.RealtimeLoader(this.loader_options); + this.rtloader_client.start(); + } + + createNew(title:string, callback: (file:any) => void) { + rtclient.createRealtimeFile(title, null, callback); + } + + loadAfterAuth(fileid:string) { + // use this as part of your afterAuth callback + rtclient.params.fileIds = fileid; + this.rtloader_client.load(); + } + } + + export class MyRealtimeDoc { + protected rtmodel: CollabModel; + protected rtdoc: CollabDoc; + private myRTLoader = new GRealtime.MyRTLoader(); + + newFile(title: string, + initializeModel: (x:CollabModel) => void, + onFileLoaded: (x:CollabDoc) => void) : void { + + var _afterAuth = () => { + this.myRTLoader.createNew(title, (file:googleRealtime.DriveAPIFileResource) => { + console.log(`\n\nThis is the createNew callback. New file's id: ${file.id}\n\n`); + $("#file-id-text-input").val(file.id); + this.myRTLoader.loadAfterAuth(file.id) + }) + } + + var _initializeModel = (model:CollabModel) => { + console.log("\n\nRTModel initialized for NEW document.\n\n"); + this.rtmodel = model; + if( initializeModel ) { + initializeModel(model); + } + } + + var _onFileLoaded = (doc:CollabDoc) => { + console.log("\n\nNEW document loaded.\n\n"); + this.rtmodel = doc.getModel(); + this.rtdoc = doc; + if( onFileLoaded ) { + onFileLoaded(doc); + } + } + + this.myRTLoader.loader_options.onFileLoaded = _onFileLoaded; + this.myRTLoader.loader_options.afterAuth = _afterAuth; + this.myRTLoader.loader_options.initializeModel = _initializeModel; + this.myRTLoader.authorize(); + } + + loadExisting(fileid: string, + onFileLoaded: (doc:CollabDoc) => void) : void { + + rtclient.params.fileIds = fileid; + + var _onFileLoaded = (doc:CollabDoc) => { + console.log("\n\nEXISTING document loaded.\n\n"); + this.rtdoc = doc; + this.rtmodel = doc.getModel(); + if( onFileLoaded ) { + onFileLoaded(doc); + } + }; + + this.myRTLoader.loader_options.onFileLoaded = _onFileLoaded; + //this.myRTLoader.loader_options.afterAuth = ... + this.myRTLoader.authorize(); + } + + createString() : CollaborativeString { return this.rtmodel.createString(""); } + + createList() : CollaborativeList { return this.rtmodel.createList(); } + + createMap() : CollaborativeMap { return this.rtmodel.createMap(); } + + addToPersistDocRoot(x:{pdata:any}, key:string) { + this.rtmodel.getRoot().set(key,x.pdata); + } + + bindString(istring:CollaborativeString, $textinput: JQuery) : googleRealtime.Binding { + return gapi.drive.realtime.databinding.bindString( + istring, + $textinput[0] ); + } + + + } + + // alternative to RealtimePSDoc.bindString + function registerLocalStringChangeListener( + x: CollaborativeString, + listener_or_callback: (e:Event) => void | EventListener) : void { + x.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, listener_or_callback); + x.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, listener_or_callback); + } + +} + + +// Next example from https://developers.google.com/google-apps/realtime/model-events + +declare var doc : CollabDoc; +function displayObjectChangedEvent(evt:googleRealtime.ObjectChangedEvent) { + var events = evt.events; + var eventCount = evt.events.length; + for (var i = 0; i < eventCount; i++) { + console.log('Event type: ' + events[i].type); + console.log('Local event: ' + events[i].isLocal); + console.log('User ID: ' + events[i].userId); + console.log('Session ID: ' + events[i].sessionId); + } +} +doc.getModel().getRoot().addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, displayObjectChangedEvent); \ No newline at end of file