Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for non-dav files #1338

Merged
merged 1 commit into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ use OCP\IRequest;

class PageController extends Controller {
protected $appName;

/** @var IEventDispatcher */
private $eventDispatcher;

public function __construct($appName,
IRequest $request,
IEventDispatcher $eventDispatcher) {
parent::__construct($appName, $request);

$this->appName = $appName;
$this->eventDispatcher = $eventDispatcher;
}
Expand All @@ -61,11 +61,11 @@ class PageController extends Controller {
This will load all the necessary scripts and make the Viewer accessible trough javascript at `OCA.Viewer`

### Open a file
1. Open a file and let the viewer fetch the folder data
1. Open a file on WebDAV and let the viewer fetch the folder data
```js
OCA.Viewer.open({path: '/path/to/file.jpg'})
```
2. Open a file and profide a list of files
2. Open a file on WebDAV and provide a list of files
```js
OCA.Viewer.open({
path: '/path/to/file.jpg',
Expand All @@ -78,8 +78,46 @@ This will load all the necessary scripts and make the Viewer accessible trough j
...
],
})
// Alternative: pass known file info so it doesn't need to be fetched
const fileInfo = {
filename: '/path/to/file.jpg',
basename: 'file.jpg',
mime: 'image/jpeg',
etag: 'xyz987',
hasPreview: true,
fileid: 13579,
}
OCA.Viewer.open({
fileinfo: fileInfo,
list: [fileInfo],
})
```
The list parameter requires an array of fileinfo. You can check how we generate a fileinfo object [here](https://github.com/nextcloud/viewer/blob/master/src/utils/fileUtils.js#L97) from a dav PROPFIND request data. There is currently no dedicated package for it, but this is coming. You can check the [photos](https://github.com/nextcloud/photos) repository where we also use it.
3. Open a file from an app's route
```js
const fileInfo1 = {
filename: 'https://next.cloud/apps/pizza/topping/pineapple.jpg',
basename: 'pineapple.jpg',
source: 'https://next.cloud/apps/pizza/topping/pineapple.jpg',
mime: 'image/jpeg',
etag: 'abc123',
hasPreview: false,
fileid: 12345,
}
const fileInfo2 = {
filename: 'https://next.cloud/apps/pizza/topping/garlic.jpg',
basename: 'garlic.jpg',
source: 'https://next.cloud/apps/pizza/topping/garlic.jpg',
mime: 'image/jpeg',
etag: 'def456',
hasPreview: false,
fileid: 67890,
}
OCA.Viewer.open({
fileInfo: fileInfo1,
list: [fileInfo1, fileInfo2],
})
```
The list parameter requires an array of fileinfo. You can check how we generate a fileinfo object [here](https://github.com/nextcloud/viewer/blob/master/src/utils/fileUtils.js#L97) from a dav PROPFIND request data. There is currently no dedicated package for it, but this is coming. You can check the [photos](https://github.com/nextcloud/photos) repository where we also uses it.

### Close the viewer
```js
Expand Down
76 changes: 76 additions & 0 deletions cypress/e2e/non-dav-files.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { randHash } from '../utils/'
const randUser = randHash()

describe('Open non-dav files in viewer', function() {
before(function() {
// Init user
cy.nextcloudCreateUser(randUser, 'password')
cy.login(randUser, 'password')

cy.visit('/apps/files')

// wait a bit for things to be settled
cy.wait(1000)
})
after(function() {
cy.logout()
})

it('Open login background', function() {
const fileInfo = {
filename: '/core/img/logo/logo.png',
basename: 'logo.png',
mime: 'image/png',
source: '/core/img/logo/logo.png',
etag: 'abc',
hasPreview: false,
fileid: 123,
}

cy.window().then((win) => {
win.OCA.Viewer.open({
fileInfo,
list: [fileInfo],
})
})
})

it('Does not see a loading animation', function() {
cy.get('body > .viewer', { timeout: 10000 })
.should('be.visible')
.and('have.class', 'modal-mask')
.and('not.have.class', 'icon-loading')
})

it('See the menu icon and title on the viewer header', function() {
cy.get('body > .viewer .modal-title').should('contain', 'logo.png')
cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible')
cy.get('body > .viewer .modal-header button.header-close').should('be.visible')
})

it('Does not see navigation arrows', function() {
cy.get('body > .viewer a.prev').should('not.be.visible')
cy.get('body > .viewer a.next').should('not.be.visible')
})
})
39 changes: 37 additions & 2 deletions cypress/e2e/visual-regression.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Visual regression tests ', function() {
cy.get('body > .viewer a.next').click()
cy.get('body > .viewer .modal-container img').should('have.length', 1)
cy.get('body > .viewer .modal-container img').should('have.attr', 'src')
cy.get('body > .viewer a.next').should('be.visible')
cy.get('body > .viewer a.prev').should('be.visible')
cy.get('body > .viewer a.next').should('be.visible')
})

Expand All @@ -112,7 +112,7 @@ describe('Visual regression tests ', function() {
cy.get('body > .viewer .modal-title').should('contain', 'test-card.png')
cy.get('body > .viewer .modal-container img').should('have.length', 1)
cy.get('body > .viewer .modal-container img').should('have.attr', 'src')
cy.get('body > .viewer a.next').should('be.visible')
cy.get('body > .viewer a.prev').should('be.visible')
cy.get('body > .viewer a.next').should('be.visible')
})

Expand All @@ -126,4 +126,39 @@ describe('Visual regression tests ', function() {
it('Take test-card.png screenshot 2', function() {
cy.compareSnapshot('image2')
})

it('Open non-dav image', function() {
const fileInfo = {
filename: '/core/img/logo/logo.png',
basename: 'logo.png',
mime: 'image/png',
source: '/core/img/logo/logo.png',
etag: 'abc',
hasPreview: false,
fileid: 123,
}

cy.window().then((win) => {
win.OCA.Viewer.open({
fileInfo,
list: [fileInfo],
})
})

cy.get('body > .viewer .modal-container img').should('have.length', 1)
cy.get('body > .viewer .modal-container img').should('have.attr', 'src')
cy.get('body > .viewer a.prev').should('not.be.visible')
cy.get('body > .viewer a.next').should('not.be.visible')
})

it('Does not see a loading animation', function() {
cy.get('body > .viewer', { timeout: 10000 })
.should('be.visible')
.and('have.class', 'modal-mask')
.and('not.have.class', 'icon-loading')
})

it('Take non-dav logo.png screenshot', function() {
cy.compareSnapshot('non-dav')
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions js/viewer-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/viewer-main.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/ImageEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default {
return {
'data-theme-dark': true,
}
}
},
},

mounted() {
Expand Down
5 changes: 5 additions & 0 deletions src/mixins/Mime.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export default {
type: String,
required: true,
},
// file source to fetch contents from
source: {
type: String,
default: undefined,
},
// file path relative to user folder
hasPreview: {
type: Boolean,
Expand Down
8 changes: 7 additions & 1 deletion src/mixins/PreviewUrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default {
previewpath() {
return this.getPreviewIfAny({
fileid: this.fileid,
source: this.source,
filename: this.filename,
hasPreview: this.hasPreview,
davPath: this.davPath,
Expand All @@ -60,12 +61,17 @@ export default {
*
* @param {object} data destructuring object
* @param {string} data.fileid the file id
* @param {?string} data.source the download source
* @param {boolean} data.hasPreview have the file an existing preview ?
* @param {string} data.davPath the absolute dav path
* @param {string} data.filename the file name
* @return {string} the absolute url
*/
getPreviewIfAny({ fileid, filename, hasPreview, davPath }) {
getPreviewIfAny({ fileid, source, filename, hasPreview, davPath }) {
if (source) {
return source
}

const searchParams = `fileId=${fileid}`
+ `&x=${Math.floor(screen.width * devicePixelRatio)}`
+ `&y=${Math.floor(screen.height * devicePixelRatio)}`
Expand Down
1 change: 1 addition & 0 deletions src/models/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default function(fileInfo, mime, component) {
failed: false,
loaded: false,
davPath: getDavPath(fileInfo),
source: fileInfo.source ?? getDavPath(fileInfo),
}

return Object.assign({}, fileInfo, data)
Expand Down
45 changes: 39 additions & 6 deletions src/services/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ import Images from '../models/images.js'
import Videos from '../models/videos.js'
import Audios from '../models/audios.js'

/**
* File info type definition
*
* @typedef {object} Fileinfo
* @property {string} filename the file name
* @property {string} basename the full path of the file
* @property {?string} source absolute path of a non-dav file, e.g. a static resource or provided by an app route
* @property {string} mime file MIME type in the format type/sub-type
* @property {string} etag WebDAV etag
* @property {boolean} hasPreview is there a WebDAV preview of this file?
* @property {number} fileid Nextcloud file ID
*/

export default class Viewer {

_state
Expand All @@ -33,6 +46,7 @@ export default class Viewer {
this._mimetypes = []
this._state = {}
this._state.file = ''
this._state.fileInfo = null
this._state.files = []
this._state.loadMore = () => ([])
this._state.onPrev = () => {}
Expand Down Expand Up @@ -81,11 +95,21 @@ export default class Viewer {
return this._state.file
}

/**
* Get the current opened file fileInfo
*
* @memberof Viewer
* @return {?Fileinfo} the currently opened file fileInfo
*/
get fileInfo() {
return this._state.fileInfo
}

/**
* Get the current files list
*
* @memberof Viewer
* @return {object[]} the currently opened file
* @return {Fileinfo[]} the current files list
*/
get files() {
return this._state.files
Expand Down Expand Up @@ -165,20 +189,24 @@ export default class Viewer {
*
* @memberof Viewer
* @param {object} options Options for opening the viewer
* @param {string} options.path path of the file to open
* @param {object[]} [options.list] the list of files as objects (fileinfo) format
* @param {?string} options.path path of the file to open
* @param {?Fileinfo} options.fileInfo file info of the file to open
* @param {Fileinfo[]} [options.list] the list of files as objects (fileinfo) format
* @param {Function} options.loadMore callback for loading more files
* @param {boolean} options.canLoop can the viewer loop over the array
* @param {Function} options.onPrev callback when navigating back to previous file
* @param {Function} options.onNext callback when navigation forward to next file
* @param {Function} options.onClose callback when closing the viewer
*/
open({ path, list = [], loadMore = () => ([]), canLoop = true, onPrev = () => {}, onNext = () => {}, onClose = () => {} } = {}) {
open({ path, fileInfo, list = [], loadMore = () => ([]), canLoop = true, onPrev = () => {}, onNext = () => {}, onClose = () => {} } = {}) {
if (typeof arguments[0] === 'string') {
throw new Error('Opening the viewer with a single string parameter is deprecated. Please use a destructuring object instead', `OCA.Viewer.open({ path: '${path}' })`)
}
if (!path && !fileInfo) {
throw new Error('Viewer needs either an URL or path to open. None given')
}

if (!path.startsWith('/')) {
if (path && !path.startsWith('/')) {
throw new Error('Please use an absolute path')
}

Expand All @@ -190,7 +218,12 @@ export default class Viewer {
throw new Error('The loadMore method must be a function')
}

this._state.file = path
// Only assign the one that is used to prevent false watcher runs
if (path) {
this._state.file = path
} else {
this._state.fileInfo = fileInfo
}
this._state.files = list
this._state.loadMore = loadMore
this._state.onPrev = onPrev
Expand Down
Loading