Lurch web app user interface

Class

FileSystem

A FileSystem is a place where files can be saved and/or loaded. This is intentionally vague, so that many different types of sources or destinations can be treated as filesystems, each with different features. Examples include each of the following, which will be implemented as subclasses:

  • A cloud storage platform such as Dropbox, which has a wide variety of features for listing, loading, saving, deleting, and renaming files.
  • The browser's localStorage object, which can store arbitrary key-value pairs, and can thus be used as a simple file system with all the same features as a cloud storage platform, but very limited space and no transferability between browsers or computers.
  • The user's hard drive, which can load and save files only with manual user intervention, and which does not let a web application list folder contents, delete files, or rename files.
  • A web repository of files, such as a GitHub repository or simply a folder on a public server, which can list files and load them, but will not let this web application save, delete, or rename files in that web repository.

This class therefore provides many abstract methods for the various actions described above (loading, saving, etc.) and is intended to be subclassed to implement specific file systems.

We formalize here tne definition of one central concept, a "file object." It will be a JavaScript object with some subset of the following fields.

  • fileSystemName - the name of the file system from which the file originated or was last saved. If this field is absent, then the file was created in the application and has not yet been saved anywhere.
  • filename - the name of the file in the file system, written in a way that is sensible for a human user to read. For example, some cloud storage systems might have unique IDs for files that are not human-readable, but by contrast this field should be the name the user gave the file. This field should be set when a file is read from a file system, and will be absent if and only if one of the following is true:
    • the user created the file in the application and has not yet saved it
    • the user saved the file to a file system that does not tell the application the name that the user chose, such as the user's hard drive through a download operation
    • the file object represents a folder, as documented below
  • UID - a unique identifier for the file in the file system. This is not necessarily a globally unique ID, but is unique within the file system named in the first attribute, above. This field should not be present if the fileSystemName field is absent, because a file system gives the file its ID, so we cannot have this field without the other first. Some file systems may be sufficiently simple that they operate using only the human-readable filename in the filename field, in which case this field can be omitted.
  • path - a string representing the path in which the file is stored. If the file system does not use paths, then this may be absent. But a file object can represent a folder by setting this field to a path but omitting the filename and UID. Such a file object can be passed as the parameter to the file listing function to list the contents of a folder, or the file open function, to specify which folder's files the user should be choosing from by default.
  • contents - the contents of the file, as a string. This field will be present if the file object is one that the application wants to save, by passing to a saving function, because obviously the file cannot be saved without its content. Similarly, the field will be present if this file object is being returned from a file loading operation, since that was the purpose of the operation. But this field may be absent if the file object is part of a directory listing, or is a parameter being passed to a file loading function, since the contents are either unnecessary or impossible in such situations.

Constructor

new FileSystem()

Construct a file system and associate it with a given TinyMCE editor.

Source

Classes

FileSystem

Methods

delete(fileObject) → {Promise}

This abstract method deletes a file from the file system. It is abstract in the sense that the base implementation returns a promise that immediately rejects with an error that the method is unimplemented. Subclasses that provide the ability to delete files must override this base implementation. Any implementation in a subclass should satisfy the following criteria.

  1. If the file object's fileSystemName does not match the name of this instance, throw an error, because the caller is asking us to delete a file in a different file system. However, if the fileSystemName was omitted, then on a successful deletion, update it to the name of this subclass.
  2. If the client passes a file object as parameter, and it contains insufficient information in its path, filename, and UID members to uniquely determine a file, throw an error. Also, if it is possible in the file system in question to detect at this point whether the file exists, and it does not, throw an error.
  3. Otherwise, delete the file from the file system and resolve to a (possibly updated) file object on success, or reject if an error occurred when attempting to delete.

Parameters

  • fileObject Object

    an object representing the file to delete, as described above, and as documented in the FileSystem class)

Returns

  • Promise

    a promise that resolves or rejects as described in the criteria above

Source

documentSaved(fileObject)

This static member should be called by any subclass that implements the write() method, whenever a save is successful, because there are two responses that the system must give to any successful file save.

  1. Delete the most recent auto-save. If we did not delete the auto-saved content, then on the next launch of the application, the user would be alerted to the fact that unsaved work existed in the auto-save file and could be recovered for them. But that would be false, because of course, they just did save their work.
  2. Store the file object representing the file just saved, so that it can be stored in the LurchDocument for the editor. If the user later invokes a "save" menu item, its event handler can use the stored file object as the parameter to the write() method.

The file object passed to this function should have enough uniquely identifying information in its filename, UID, and path members to satisfy the requirements of the write() method for the same file system subclass. If its contents member has data in it, that data will be ignored, so that it is not unnecessarily copied. No fileSystemName needs to be provided; each subclass will use its own.

Parameters

  • fileObject Object

    the file object representing the file that was just saved (and whose format is documented in the FileSystem class)

See

Source

fileChooserItems() → nullable{Array.<Object>}

When the user wants to select a file from this file system, this method returns a list of dialog items allowing the user to choose a file. For example, if the list of files is known, the UI might be a representation of that list, allowing the user to click one. Or if the file system is the web, from which one downloads URLs, the UI might be a text box into which one can type a URL.

The base class implementation is to return a single file chooser item (in an array by itself) if the file system implements the list() method, and undefined otherwise.

Anyone reimplementing this function must ensure that, whenever the user interacts with the dialog items to choose a file, or change which file has been chosen, the event handlers in one or more of the items returned by this function must notify the dialog of what has changed by calling dialog.selectFile(fileObject). The parameter should either be a file object as documented at the top of this class, or it should be omitted to indicate that no (valid) file is currently selected. This same function should be called during one of the items' onShow() handlers as well, to initialize which file is selected when the tab containing these dialog items first appears. Failure to follow this convention will result in undefined behavior.

If the UI this function returns is only for selecting a file, but not loading its contents, the file object set with dialog.selectFile() may contain just the name and/or UID fields, and need not contain the contents field. It can be loaded later with a call to read(). If the UI this function returns is for loading a file (e.g., drag-and-drop a file from the user's computer to upload it) then the file object is free to include the contents as well, especially since they cannot be read directly from JavaScript in that example case.

If the client wants the UI to browse to a specific location in the file system, it can pass a file object with the path field set to the location at which browsing should begin.

Returns

  • Array.<Object>

    a list of dialog items representing this file system in a dialog, if the user's intent is to select a file from it

Source

fileSaverItems() → nullable{Array.<Object>}

When the user wants to save a file to this file system, this method returns a list of dialog items allowing the user to choose the location for the save. For example, the UI might be a list of existing files, together with a text blank into which you can type the filename of the new file to save (or fill that box by clicking the name of an existing file to save over it) just like many existing File-Save dialogs.

The base class implementation is to return two dialog items that behave as in the example above, one text box and one file chooser item (in an array of length two) if the file system implements the list() method, and just the text box alone if not.

Anyone reimplementing this function must ensure that, whenever the user interacts with the dialog items to choose a save destination, the event handlers in one or more of the items returned by this function must notify the dialog of what has changed by calling dialog.setLocation(fileObject). The parameter should either be a file object as documented at the top of this class, or it should be omitted to indicate that no (valid) destination is currently specified. This same function should be called during one of the items' onShow() handlers as well, to initialize which destination is specified when the tab containing these dialog items first appears. (In many cases, no file will be chosen initially, and you can call dialog.setLocation() with no argument.) Failure to follow this convention will result in undefined behavior.

Calls to dialog.setLocation() never need to pass a file object with a contents field, since the contents can be filled in afterwards by the caller, and before a call to write().

If the client wants the UI to start out referring to a specific location in the file system, such as the last folder or file where the user saved something, it can pass a file object with the filename and/or path fields set to the file or folder at which browsing should begin.

Returns

  • Array.<Object>

    a list of dialog items representing this file system in a dialog, if the user's intent is to save a file into it

Source

getName() → {string}

Get the name of the class of this file system, by looking its class up in the list of registered subclasses. For more information about registering subclasses, see the registerSubclass() static member.

Returns

  • string

    the name of the class of this file system (or undefined if this instance is a member of a subclass that was not registered)

Source

has(fileObject) → {Promise}

This abstract method answers the question of whether the file system contains a file with the criteria specified in the parameter. It is abstract in the sense that the base implementation returns a promise that immediately rejects with an error that the method is unimplemented. Subclasses that provide the ability to read files (by implementing the read() method) should also provide this method by overriding this base implementation. Any implementation in a subclass should satisfy the following criteria.

  1. If the client passes a file object as parameter, and it contains sufficient information in its path, filename, and UID members to uniquely determine a file, then return a promise that checks whether the specified file exists in the file sytem and resolves to the result, as a boolean value. The promise should reject only if an error occurs when attempting to check whether the file exists.
  2. In all other cases, throw an error. This includes a missing parameter, insufficient information in the parameter, or a parameter whose fileSystemName does not match the name of this file system.

Parameters

  • fileObject Object

    an object representing the file being queried, as described above, and as documented in the FileSystem class)

See

Returns

  • Promise

    a promise that resolves or rejects as described in the criteria above

Source

implements(name) → {boolean}

The following member functions of the FileSystem class are abstract, and thus have empty implementations in the base class: read, write, delete, has, and list. Subclasses may choose to implement some of them, as documented in the class itself. You can test whether a specific instance implements a given feature by calling this function. You can test whether a specific subclass implements a given feature by calling subclassImplements().

Parameters

  • name string

    the name of the feature, from the list above

Returns

  • boolean

    whether the instance implements the given feature

Source

list(fileObjectopt) → {Promise}

This abstract method returns a promise that resolves to a list of objects representing all files in the file system. It is abstract in the sense that the base implementation returns a promise that immediately rejects with an error that the method is unimplemented. Subclasses that provide the ability to read files (by implementing the read() method) should also provide this method by overriding this base implementation. Any implementation in a subclass should satisfy the following criteria.

  1. If the client omits the parameter, then return a promise that gets the list of all files in the root of the file system and resolves to a JavaScript array of file objects (which are documented in the FileSystem class). The promise should reject only if an error occurs when attempting to get the list of files. Note that a folder may contain subfolders, and those can be included in the list of results returned, because file objects have the capability of representing folders as well. For details, see the documentation for the FileSystem class.
  2. If the client passes a file object as parameter, and it contains a path member, then proceed exactly as in item 1., above, but not in the root of the file system, rather in the path provided. If such a path does not exist, reject with an error.

Parameters

  • fileObject Object <optional>

    an object representing the path whose files should be listed, or if omitted, the root of the file system is assumed instead

See

Returns

  • Promise

    a promise that resolves or rejects as described in the criteria above

Source

read(fileObjectopt) → {Promise}

This abstract method reads a file from the file system. It is abstract in the sense that the base implementation returns a promise that immediately rejects with an error that the method is unimplemented. Subclasses that provide the ability to read files must override this base implementation. Any implementation in a subclass should satisfy the following criteria.

  1. If the user passes a file object parameter (as documented in the FileSystem class) with enough information in it to identify a file, then this method should return a promise that resolves to that file as soon as it can be loaded. Specifically, the object to which the promise resolves should be the same object as the one passed in, but with its contents member set to the contents of the file, as a string. Furthermore, the file object should have its file system name set to the name of the subclass in question.
  2. If the user omits the parameter, or omits its filename or UID, or provides an invalid file object in any other way, then this method should reject with an error, because the user did not specify which file to read.

Parameters

  • fileObject Object <optional>

    an object representing the file to read, as described above

Returns

  • Promise

    a promise that resolves or rejects as described in the criteria above

Source

write(fileObject)

This abstract method saves a file to the file system. It is abstract in the sense that the base implementation returns a promise that immediately rejects with an error that the method is unimplemented. Subclasses that provide the ability to save files must override this base implementation. Any implementation in a subclass should satisfy the following criteria.

  1. If the file object's fileSystemName does not match the name of this instance, throw an error, because the caller is asking us to write in a place to which we have no access. However, if the fileSystemName was omitted, then on a successful save, update it to the name of this subclass.
  2. If the file object's contents member is undefined, throw an error, because we have no content to write.
  3. If the file object contains insufficient information in its filename, UID, and path members to let this file system know where to write the content, throw an error.
  4. Save the given contents into the file system and resolve to a (possibly updated) file object on success, or reject if an error occurred when attempting to write.

Parameters

  • fileObject Object

    an object representing the file to write, as described above, and as documented in the FileSystem class)

Source

static

deleteFile(editor)

Open a Delete File dialog, with tabs for each file system that supports the deletion of files, and allow the user to browse them for a file to delete. If the user chooses a file and asks to delete it, attempt to do so, and pop up a notification indicating success or failure.

Parameters

  • editor tinymce.Editor

    the editor over which the dialog will be shown (but the editor's contents to not play into this deletion operation)

Source

static

getNamedSubclasses(names) → {Array}

Get a list of just those subclasses whose names are given, in the order specified. This is useful when the app's settings request only a certain set of file systems to be available.

If any of the names is not the name of a valid subclass, an error will be thrown.

Parameters

  • names Array

    the names of the subclasses to get

Returns

  • Array

    the list of subclasses with the given names

Source

static

getSubclass(name) → {Object}

Get a FileSystem subclass by name. For more information about registering subclasses, see the registerSubclass() static member.

Parameters

  • name string

    the name of the subclass to get

See

Returns

  • Object

    the subclass with the given name

Source

static

getSubclassName(subclass) → {string}

Get the name of a given FileSystem subclass, by looking it up in the list of registered subclasses. For more information about registering subclasses, see the registerSubclass() static member.

Parameters

  • subclass Object

    the subclass to look up

See

Returns

  • string

    the name of the given subclass (or undefined if the given object is not a subclass that was registered)

Source

static

getSubclasses() → {Array}

Get a list of all registered file systems. For more information about registering subclasses, see the registerSubclass() static member.

Returns

  • Array

    the list of all registered file systems

Source

static

openFile(editor, callbackopt)

Open a File > Open dialog over the given editor, with tabs for each available file system, and allow the user to browse them for a file to open. If the user chooses a file and asks to open it, attempt to do so, and as long as it succeeds, replace the editor's current contents with that new document. If anything goes wrong, show a notification to the user stating what went wrong. If it succeeds, show a brief success notification after the file loads.

An alternate use of this function is to open a file for some other purpose, and just pass the contents of the file to a callback, without actually changing teh contents of the given editor. To use this function in that way, pass a callback as the second argument. It will be called when the dialog closes, either with a file object or null if the user did not choose a file (e.g., canceled the dialog).

Parameters

  • editor tinymce.Editor

    the editor in which to open the file

  • callback function <optional>

    the callback to call when the dialog closes, which can be omitted for the standard file-loading behavior

Source

static

registerSubclass(name, subclass)

This class tracks its collection of subclasses so that we can find a file system by its name, or get a list of all file systems registered. We may need to find a file system by name if we have a file we need to save into that system (which knows the name of the file system it came from) and we may need to get a list of all file systems to populate the submenus for file open, file save, etc.

Example of registering a subclass:

class Example extends FileSystem { ... }
FileSystem.registerSubclass( 'Example', Example )

Parameters

  • name string

    the name of the subclass to register

  • subclass Object

    the subclass itself

See

Source

static

saveFileAs(editor)

Open a File > Save As dialog over the given editor, with tabs for each available file system, and allow the user to browse them for a location into which to save their current file. If the user chooses a location and asks to save into it, attempt to do so, and pop up a notification indicating success or failure. Upon success, update the file information stored in the current editor about where the document has been saved, and notify the document that it is not dirty, using documentSaved().

Parameters

  • editor tinymce.Editor

    the editor whose file is to be saved

Source

static

subclassImplements(subclass, name) → {boolean}

The following member functions of the FileSystem class are abstract, and thus have empty implementations in the base class: read, write, delete, has, and list. Subclasses may choose to implement some of them, as documented in the class itself. You can test whether a specific subclass implements a given feature by calling this function. You can test whether a specific instance implements a given feature by calling implements().

Parameters

  • subclass Object

    the subclass to test

  • name string

    the name of the feature, from the list above

Returns

  • boolean

    whether the subclass implements the given feature

Source

static

subclassesImplementing(name) → {Array}

The following member functions of the FileSystem class are abstract, and thus have empty implementations in the base class: read, write, delete, has, and list. Subclasses may choose to implement some of them, as documented in the class itself. You can get the full list of subclasses that implement a given feature by calling this function.

Parameters

  • name string

    the name of the feature, from the list above

Returns

  • Array

    the list of subclasses that implement the given feature

Source