Skip to content

Commit

Permalink
Update: Allow passed in collections to be of file objects (#359)
Browse files Browse the repository at this point in the history
Also allows tokens to be null or undefined. Useful when the
passed in file or collection has well formed file objects.

Also changes the token getter to pass in typed ids for files
and handle cases when typed ids are returned from the
token service to make it consistant with other UI Elements.
Since all the UI elements can either be requesting folder
or file ids, the token service needs to know what type of
id we are sending it, as such either file_ or folder_ is
prefixed to all the ids. Likewise the token service will
return typed ids after fetching tokens.
  • Loading branch information
priyajeet authored Sep 12, 2017
1 parent bef23db commit cc59009
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 116 deletions.
109 changes: 66 additions & 43 deletions src/lib/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,20 @@ class Preview extends EventEmitter {
* @return {void}
*/
show(fileIdOrFile, token, options = {}) {
// Save a reference to the options to be used later
if (typeof token === 'string' || typeof token === 'function') {
// Save a reference to the options to be re-used later.
// Token should either be a function or a string.
// Token can also be null or undefined for offline use case.
// But it cannot be a random object.
if (token === null || typeof token !== 'object') {
this.previewOptions = Object.assign({}, options, { token });
} else {
throw new Error('Missing access token!');
throw new Error('Bad access token!');
}

// Update the optional file navigation collection and caches
// if proper valid file objects were passed in.
this.updateCollection(options.collection);

// Load the preview
this.load(fileIdOrFile);
}
Expand All @@ -187,16 +194,41 @@ class Preview extends EventEmitter {
}

/**
* Updates files to navigate between.
* Updates files to navigate between. Collection can be of files
* or file ids or a mix. We normalize here to file ids for easier
* indexing and cache only the well-formed file objects if provided.
*
* @public
* @param {string[]} [collection] - Updated collection of file IDs
* @param {string[]} [collection] - Updated collection of file or file IDs
* @return {void}
*/
updateCollection(collection = []) {
this.collection = Array.isArray(collection) ? collection : [];
// Also update the original collection that was saved from the initial show
this.previewOptions.collection = this.collection;
updateCollection(collection) {
const fileOrIds = Array.isArray(collection) ? collection : [];
const files = [];
const fileIds = [];

fileOrIds.forEach((fileOrId) => {
if (fileOrId && typeof fileOrId === 'string') {
// String id found in the collection
fileIds.push(fileOrId);
} else if (fileOrId && typeof fileOrId === 'object' && typeof fileOrId.id === 'string') {
// Possible well-formed file object found in the collection
fileIds.push(fileOrId.id);
files.push(fileOrId);
} else {
throw new Error('Bad collection provided!');
}
});

// Update the cache with possibly well-formed file objects.
this.updateFileCache(files);

// Collection always uses string ids for easier indexing.
this.collection = fileIds;

// Since update collection is a public method, it can be
// called anytime to update navigation. If we are showing
// a preview already show or hide the navigation arrows.
if (this.file) {
this.ui.showNavigation(this.file.id, this.collection);
}
Expand Down Expand Up @@ -254,7 +286,7 @@ class Preview extends EventEmitter {
}

/**
* Returns the current file being previewed.
* Returns the current collection of files that preview is aware of.
*
* @public
* @return {Object|null} Current collection
Expand Down Expand Up @@ -466,21 +498,23 @@ class Preview extends EventEmitter {
* @return {void}
*/
prefetchViewers(viewerNames = []) {
this.getViewers().filter((viewer) => viewerNames.indexOf(viewer.NAME) !== -1).forEach((viewer) => {
const viewerInstance = new viewer.CONSTRUCTOR(
this.createViewerOptions({
viewer
})
);

if (typeof viewerInstance.prefetch === 'function') {
viewerInstance.prefetch({
assets: true,
preload: false,
content: false
});
}
});
this.getViewers()
.filter((viewer) => viewerNames.indexOf(viewer.NAME) !== -1)
.forEach((viewer) => {
const viewerInstance = new viewer.CONSTRUCTOR(
this.createViewerOptions({
viewer
})
);

if (typeof viewerInstance.prefetch === 'function') {
viewerInstance.prefetch({
assets: true,
preload: false,
content: false
});
}
});
}

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -517,6 +551,9 @@ class Preview extends EventEmitter {
} else if (checkFileValid(fileIdOrFile)) {
// Use well-formed file object if available
this.file = fileIdOrFile;
} else if (!!fileIdOrFile && typeof fileIdOrFile.id === 'string') {
// File is not a well-formed file object but has an id
this.file = { id: fileIdOrFile.id };
} else {
throw new Error(
'File is not a well-formed Box File object. See FILE_FIELDS in file.js for a list of required fields.'
Expand All @@ -530,14 +567,10 @@ class Preview extends EventEmitter {
this.retryCount = 0;
}

if (typeof fileIdOrFile === 'object') {
this.loadPreviewWithTokens({});
} else {
// Fetch access tokens before proceeding
getTokens(this.file.id, this.previewOptions.token)
.then(this.loadPreviewWithTokens)
.catch(this.triggerFetchError);
}
// Fetch access tokens before proceeding
getTokens(this.file.id, this.previewOptions.token)
.then(this.loadPreviewWithTokens)
.catch(this.triggerFetchError);
}

/**
Expand Down Expand Up @@ -573,13 +606,6 @@ class Preview extends EventEmitter {
// Update navigation
this.ui.showNavigation(this.file.id, this.collection);

// If preview collection is empty, create a collection of one with the
// current file ID. Otherwise, assume the current file ID is already in
// the collection.
if (this.collection.length === 0) {
this.collection = [this.file.id];
}

if (checkFileValid(this.file)) {
// Save file in cache. This also adds the 'ORIGINAL' representation.
cacheFile(this.cache, this.file);
Expand Down Expand Up @@ -640,9 +666,6 @@ class Preview extends EventEmitter {
// Custom Box3D application definition
this.options.box3dApplication = options.box3dApplication;

// Save the files to iterate through
this.collection = options.collection || [];

// Save the reference to any additional custom options for viewers
this.options.viewers = options.viewers || {};

Expand Down
95 changes: 69 additions & 26 deletions src/lib/__tests__/Preview-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ describe('lib/Preview', () => {
describe('show()', () => {
beforeEach(() => {
stubs.load = sandbox.stub(preview, 'load');
stubs.updateCollection = sandbox.stub(preview, 'updateCollection');
});

it('should set the preview options with string token', () => {
Expand All @@ -110,6 +111,7 @@ describe('lib/Preview', () => {
});
});


it('should set the preview options with function token', () => {
const foo = () => {};
preview.show('123', foo, { viewer: 'viewer' });
Expand All @@ -119,11 +121,35 @@ describe('lib/Preview', () => {
});
});

it('should set the preview options with null token', () => {
preview.show('123', null);
expect(preview.previewOptions).to.deep.equal({
token: null
});
});

it('should set the preview options with no token', () => {
preview.show('123');
expect(preview.previewOptions).to.deep.equal({
token: undefined
});
});

it('should call update collection with optional collection', () => {
preview.show('123', 'token', { collection: 'collection' });
expect(stubs.updateCollection).to.be.calledWith('collection');
});

it('should load file associated with the passed in file ID', () => {
preview.show('123', 'token');
expect(stubs.load).to.be.calledWith('123');
});

it('should call update collection with passed in collection', () => {
preview.show('123', 'token', { collection: 'collection' });
expect(stubs.updateCollection).to.be.calledWith('collection');
});

it('should load file matching the passed in file object', () => {
const file = {
id: '123',
Expand All @@ -143,14 +169,14 @@ describe('lib/Preview', () => {
expect(stubs.load).to.be.calledWith(file);
});

it('should throw an error if there is no auth token', () => {
it('should throw an error if auth token is a random object', () => {
const spy = sandbox.spy(preview, 'show');

try {
preview.show('123', {});
} catch (e) {
expect(spy.threw());
expect(e.message).to.equal('Missing access token!');
expect(e.message).to.equal('Bad access token!');
}
});
});
Expand Down Expand Up @@ -189,20 +215,45 @@ describe('lib/Preview', () => {
describe('updateCollection()', () => {
beforeEach(() => {
stubs.showNavigation = sandbox.stub(preview.ui, 'showNavigation');
stubs.updateFileCache = sandbox.stub(preview, 'updateFileCache');
});

it('should set the preview and preview options collection to an array', () => {
let array = [1, 2, 3, 4];
it('should set the preview collection to an array of file ids', () => {
let array = ['1', '2', '3', '4'];

preview.updateCollection(array);
expect(stubs.updateFileCache).to.be.calledWith([]);
expect(preview.collection).to.deep.equal(array);
expect(preview.previewOptions.collection).to.deep.equal(array);
});

it('should set the preview collection to an array of file ids when files passed in', () => {
let files = ['1', { id: '2' }, '3', { id: '4' }, { id: '5' }];

preview.updateCollection(files);
expect(stubs.updateFileCache).to.be.calledWith([{ id: '2' }, { id: '4' }, { id: '5' }]);
expect(preview.collection).to.deep.equal(['1', '2', '3', '4', '5']);
});

it('should throw when bad array of files passed in', () => {
let files = ['1', { }, '3'];

array = '1,2,3,4';
expect(preview.updateCollection.bind(preview, files)).to.throw(Error, /Bad collection/);
expect(stubs.updateFileCache).to.not.be.called;
});

it('should throw when bad array of file ids passed in', () => {
let files = ['', '3'];

expect(preview.updateCollection.bind(preview, files)).to.throw(Error, /Bad collection/);
expect(stubs.updateFileCache).to.not.be.called;
});

it('should reset the preview collection to an empty array', () => {
let array = '1,2,3,4';

preview.updateCollection(array);
expect(stubs.updateFileCache).to.be.calledWith([]);
expect(preview.collection).to.deep.equal([]);
expect(preview.previewOptions.collection).to.deep.equal([]);
});

it('should show navigation if the file exists', () => {
Expand Down Expand Up @@ -675,12 +726,6 @@ describe('lib/Preview', () => {
expect(preview.retryTimeout).to.equal(undefined);
});

it('should load preview when a well-formed file object is passed', () => {
preview.load(stubs.file);
expect(stubs.loadPreviewWithTokens).to.be.calledWith({});
expect(stubs.getTokens).to.not.be.called;
});

it('should set the retry count', () => {
preview.retryCount = 0;
preview.file.id = '0';
Expand All @@ -697,17 +742,21 @@ describe('lib/Preview', () => {
it('should throw an error if incompatible file object is passed in', () => {
const spy = sandbox.spy(preview, 'load');
const file = {
id: '123',
not: 'the',
right: 'fields'
}

try {
preview.load(file);
} catch (e) {
expect(spy.threw());
expect(e.message).to.equal('File is not a well-formed Box File object. See FILE_FIELDS in file.js for a list of required fields.');
}
expect(preview.load.bind(preview, file)).to.throw(Error, 'File is not a well-formed Box File object. See FILE_FIELDS in file.js for a list of required fields.');
});

it('should get the tokens when file id is available', () => {
preview.previewOptions.token = 'token';

preview.load({ id: '123' });
return stubs.promise.then(() => {
expect(stubs.getTokens).to.be.calledWith('123', 'token');
expect(stubs.loadPreviewWithTokens).to.be.called;
});
});

it('should get the tokens and either handle the response or error', () => {
Expand Down Expand Up @@ -885,12 +934,6 @@ describe('lib/Preview', () => {
expect(preview.options.skipServerUpdate).to.be.true;
});

it('should save the files to iterate through and any options for custom viewers', () => {
preview.parseOptions(preview.previewOptions, stubs.tokens);
expect(preview.collection).to.equal(stubs.collection);
expect(preview.options.viewers instanceof Object).to.be.true;
});

it('should add user created loaders before standard loaders', () => {
const expectedLoaders = stubs.loaders.concat(loaders);

Expand Down
Loading

0 comments on commit cc59009

Please sign in to comment.