Skip to content

Commit ffd1c24

Browse files
authored
Merge pull request #1835 from nextcloud/feat/version-comparison
2 parents 809ab23 + 9a90d30 commit ffd1c24

File tree

8 files changed

+124
-11
lines changed

8 files changed

+124
-11
lines changed

js/viewer-filerobot.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/viewer-filerobot.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/viewer-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/viewer-main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/services/FileList.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import type { FileStat, ResponseDataDetailed } from 'webdav'
2626

2727
/**
2828
* Retrieve the files list
29+
* @param path
30+
* @param options
2931
*/
3032
export default async function(path: string, options = {}): Promise<FileInfo[]> {
3133
// getDirectoryContents doesn't accept / for root

src/services/Viewer.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ import Images from '../models/images.js'
2424
import Videos from '../models/videos.js'
2525
import Audios from '../models/audios.js'
2626

27+
/**
28+
* Handler type definition
29+
*
30+
* @typedef {object} Handler
31+
* @property {string} id unique identifier for the handler
32+
* @property {string[]} mimes list of mime types that are supported for opening
33+
* @property {object} component Vue component to render the file
34+
* @property {string} group group identifier to combine for navigating to the next/previous files
35+
* @property {?string} theme viewer modal theme (one of 'dark', 'light', 'default')
36+
* @property {boolean} canCompare Indicate support for comparing two files
37+
*/
38+
2739
/**
2840
* File info type definition
2941
*
@@ -41,12 +53,15 @@ export default class Viewer {
4153

4254
_state
4355
_mimetypes
56+
_mimetypesCompare
4457

4558
constructor() {
4659
this._mimetypes = []
60+
this._mimetypesCompare = []
4761
this._state = {}
4862
this._state.file = ''
4963
this._state.fileInfo = null
64+
this._state.compareFileInfo = null
5065
this._state.files = []
5166
this._state.enableSidebar = true
5267
this._state.el = null
@@ -71,6 +86,7 @@ export default class Viewer {
7186
*
7287
* @readonly
7388
* @memberof Viewer
89+
* @return {Handler[]}
7490
*/
7591
get availableHandlers() {
7692
return this._state.handlers
@@ -80,11 +96,14 @@ export default class Viewer {
8096
* Register a new handler
8197
*
8298
* @memberof Viewer
83-
* @param {object} handler a new unregistered handler
99+
* @param {Handler} handler a new unregistered handler
84100
*/
85101
registerHandler(handler) {
86102
this._state.handlers.push(handler)
87103
this._mimetypes.push.apply(this._mimetypes, handler.mimes)
104+
if (handler?.canCompare === true) {
105+
this._mimetypesCompare.push.apply(this._mimetypesCompare, handler.mimes)
106+
}
88107
}
89108

90109
/**
@@ -107,6 +126,16 @@ export default class Viewer {
107126
return this._state.fileInfo
108127
}
109128

129+
/**
130+
* Get the current comparison view opened file fileInfo
131+
*
132+
* @memberof Viewer
133+
* @return {?Fileinfo} the currently opened file fileInfo
134+
*/
135+
get compareFileInfo() {
136+
return this._state.compareFileInfo
137+
}
138+
110139
/**
111140
* Get the current files list
112141
*
@@ -147,6 +176,16 @@ export default class Viewer {
147176
return this._mimetypes
148177
}
149178

179+
/**
180+
* Get the supported mimetypes that can be opened side by side for comparison
181+
*
182+
* @memberof Viewer
183+
* @return {Array} list of mimetype strings that the viewer can open side by side for comparison
184+
*/
185+
get mimetypesCompare() {
186+
return this._mimetypesCompare
187+
}
188+
150189
/**
151190
* Return the method provided to fetch more results
152191
*
@@ -291,6 +330,20 @@ export default class Viewer {
291330
this.open(options)
292331
}
293332

333+
/**
334+
* Open the viewer with two files side by side
335+
*
336+
* @memberof Viewer
337+
* @param {Fileinfo} fileInfo current file
338+
* @param {Fileinfo} compareFileInfo older file to compare
339+
*/
340+
compare(fileInfo, compareFileInfo) {
341+
this.open({
342+
fileInfo,
343+
})
344+
this._state.compareFileInfo = compareFileInfo
345+
}
346+
294347
/**
295348
* Close the opened file
296349
*

src/utils/fileUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export type FileInfo = object
8282
/**
8383
* Generate a fileinfo object based on the full dav properties
8484
* It will flatten everything and put all keys to camelCase
85+
* @param obj
8586
*/
8687
const genFileInfo = function(obj: FileStat): FileInfo {
8788
const fileInfo = {}

src/views/Viewer.vue

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,23 @@
111111
</NcActionButton>
112112
</template>
113113

114-
<div class="viewer__content" @click.self.exact="close">
114+
<div class="viewer__content" :class="contentClass" @click.self.exact="close">
115+
<!-- COMPARE FILE -->
116+
<component :is="comparisonFile.modal"
117+
v-if="comparisonFile && !comparisonFile.failed && showComparison"
118+
:key="comparisonFile | uniqueKey"
119+
ref="comparison-content"
120+
v-bind="comparisonFile"
121+
:active="true"
122+
:can-swipe="false"
123+
:can-zoom="false"
124+
:editing="false"
125+
:is-full-screen="isFullscreen"
126+
:is-sidebar-shown="isSidebarShown"
127+
:loaded.sync="comparisonFile.loaded"
128+
class="viewer__file viewer__file--active"
129+
@error="comparisonFailed" />
130+
115131
<!-- PREVIOUS -->
116132
<component :is="previousFile.modal"
117133
v-if="previousFile && !previousFile.failed"
@@ -236,6 +252,7 @@ export default {
236252
currentIndex: 0,
237253
previousFile: {},
238254
currentFile: {},
255+
comparisonFile: null,
239256
nextFile: {},
240257
fileList: [],
241258
@@ -280,6 +297,9 @@ export default {
280297
fileInfo() {
281298
return this.Viewer.fileInfo
282299
},
300+
comparisonFileInfo() {
301+
return this.Viewer.compareFileInfo
302+
},
283303
files() {
284304
return this.Viewer.files
285305
},
@@ -345,7 +365,7 @@ export default {
345365
* @return {boolean}
346366
*/
347367
canDownload() {
348-
return canDownload()
368+
return canDownload() && !this.comparisonFile
349369
},
350370
351371
/**
@@ -359,6 +379,7 @@ export default {
359379
&& canDownload()
360380
&& this.currentFile?.permissions?.includes('W')
361381
&& this.isImage
382+
&& !this.comparisonFile
362383
},
363384
364385
modalClass() {
@@ -371,6 +392,16 @@ export default {
371392
'image--fullscreen': this.isImage && this.isFullscreenMode,
372393
}
373394
},
395+
396+
showComparison() {
397+
return !this.isMobile
398+
},
399+
400+
contentClass() {
401+
return {
402+
'viewer--split': this.comparisonFile,
403+
}
404+
},
374405
},
375406
376407
watch: {
@@ -412,6 +443,16 @@ export default {
412443
}
413444
},
414445
446+
comparisonFileInfo(fileInfo) {
447+
if (fileInfo) {
448+
logger.info('Opening viewer for comparisonFileInfo ', { fileInfo })
449+
this.compareFile(fileInfo)
450+
} else {
451+
// object is undefined, we're closing!
452+
this.cleanup()
453+
}
454+
},
455+
415456
files(fileList) {
416457
// the files list changed, let's update the current opened index
417458
const currentIndex = fileList.findIndex(file => file.basename === this.currentFile.basename)
@@ -522,7 +563,7 @@ export default {
522563
this.cancelRequestFile()
523564
524565
// do not open the same file again
525-
if (path === this.currentFile.path) {
566+
if (path === this.currentFile.path && !this.currentFile.source) {
526567
return
527568
}
528569
@@ -573,7 +614,7 @@ export default {
573614
this.cancelRequestFolder()
574615
575616
// do not open the same file info again
576-
if (fileInfo.basename === this.currentFile.basename) {
617+
if (fileInfo.basename === this.currentFile.basename && fileInfo.source !== this.currentFile.source) {
577618
return
578619
}
579620
@@ -643,6 +684,7 @@ export default {
643684
644685
// show file
645686
this.currentFile = new File(fileInfo, mime, handler.component)
687+
this.comparisonFile = null
646688
this.updatePreviousNext()
647689
648690
// if sidebar was opened before, let's update the file
@@ -662,6 +704,10 @@ export default {
662704
this.updatePreviousNext()
663705
},
664706
707+
async compareFile(fileInfo) {
708+
this.comparisonFile = new File(fileInfo, fileInfo.mime, this.components[fileInfo.mime])
709+
},
710+
665711
/**
666712
* Show sidebar if available and a file is already opened
667713
*/
@@ -877,6 +923,7 @@ export default {
877923
cleanup() {
878924
// reset all properties
879925
this.currentFile = {}
926+
this.comparisonFile = null
880927
this.currentModal = null
881928
this.fileList = []
882929
this.initiated = false
@@ -939,6 +986,10 @@ export default {
939986
/**
940987
* Failures handlers
941988
*/
989+
comparisonFailed() {
990+
this.comparisonFile.failed = true
991+
},
992+
942993
previousFailed() {
943994
this.previousFile.failed = true
944995
},
@@ -1091,6 +1142,12 @@ export default {
10911142
cursor: pointer;
10921143
}
10931144
1145+
&--split {
1146+
.viewer__file--active {
1147+
width: 50%;
1148+
}
1149+
}
1150+
10941151
:deep(.modal-wrapper) {
10951152
.modal-container {
10961153
// Ensure some space at the bottom

0 commit comments

Comments
 (0)