Skip to content

Commit 3d7bfae

Browse files
committed
feat: redirect to the mime icon if no preview available
Signed-off-by: John Molakvoæ <[email protected]>
1 parent db2a576 commit 3d7bfae

File tree

9 files changed

+166
-22
lines changed

9 files changed

+166
-22
lines changed

apps/files/src/components/FileEntry.vue

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@
4545
class="files-list__row-icon-preview"
4646
:style="{ backgroundImage }" />
4747

48-
<span v-else-if="mimeIconUrl"
49-
class="files-list__row-icon-preview files-list__row-icon-preview--mime"
50-
:style="{ backgroundImage: mimeIconUrl }" />
51-
5248
<FileIcon v-else />
5349

5450
<!-- Favorite icon -->
@@ -155,17 +151,16 @@
155151
</template>
156152

157153
<script lang='ts'>
154+
import { CancelablePromise } from 'cancelable-promise'
158155
import { debounce } from 'debounce'
159156
import { emit } from '@nextcloud/event-bus'
160157
import { extname } from 'path'
161158
import { formatFileSize, Permission } from '@nextcloud/files'
162-
import { Fragment } from 'vue-frag'
163159
import { generateUrl } from '@nextcloud/router'
164160
import { showError, showSuccess } from '@nextcloud/dialogs'
165161
import { translate } from '@nextcloud/l10n'
166162
import { vOnClickOutside } from '@vueuse/components'
167163
import axios from '@nextcloud/axios'
168-
import CancelablePromise from 'cancelable-promise'
169164
import FileIcon from 'vue-material-design-icons/File.vue'
170165
import FolderIcon from 'vue-material-design-icons/Folder.vue'
171166
import moment from '@nextcloud/moment'
@@ -205,7 +200,6 @@ export default Vue.extend({
205200
FavoriteIcon,
206201
FileIcon,
207202
FolderIcon,
208-
Fragment,
209203
NcActionButton,
210204
NcActions,
211205
NcCheckboxRadioSwitch,
@@ -394,6 +388,7 @@ export default Vue.extend({
394388
// Request tiny previews
395389
url.searchParams.set('x', '32')
396390
url.searchParams.set('y', '32')
391+
url.searchParams.set('mimeFallback', 'true')
397392
398393
// Handle cropping
399394
url.searchParams.set('a', this.cropPreviews === true ? '0' : '1')
@@ -402,14 +397,6 @@ export default Vue.extend({
402397
return null
403398
}
404399
},
405-
mimeIconUrl() {
406-
const mimeType = this.source.mime || 'application/octet-stream'
407-
const mimeIconUrl = window.OC?.MimeType?.getIconUrl?.(mimeType)
408-
if (mimeIconUrl) {
409-
return `url(${mimeIconUrl})`
410-
}
411-
return ''
412-
},
413400
414401
// Sorted actions that are enabled for this node
415402
enabledActions() {

apps/files/src/components/VirtualList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<tbody :style="tbodyStyle" class="files-list__tbody">
1515
<component :is="dataComponent"
1616
v-for="(item, i) in renderedItems"
17-
:key="item[dataKey]"
17+
:key="i"
1818
:active="(i >= bufferItems || index <= bufferItems) && (i < shownItems - bufferItems)"
1919
:source="item"
2020
:index="i"

core/Controller/PreviewController.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@
3232
use OCP\AppFramework\Http;
3333
use OCP\AppFramework\Http\DataResponse;
3434
use OCP\AppFramework\Http\FileDisplayResponse;
35+
use OCP\AppFramework\Http\RedirectResponse;
3536
use OCP\Files\File;
3637
use OCP\Files\IRootFolder;
3738
use OCP\Files\Node;
3839
use OCP\Files\NotFoundException;
3940
use OCP\IPreview;
4041
use OCP\IRequest;
42+
use OCP\Preview\IMimeIconProvider;
4143

4244
class PreviewController extends Controller {
4345
public function __construct(
@@ -46,6 +48,7 @@ public function __construct(
4648
private IPreview $preview,
4749
private IRootFolder $root,
4850
private ?string $userId,
51+
private IMimeIconProvider $mimeIconProvider,
4952
) {
5053
parent::__construct($appName, $request);
5154
}
@@ -62,6 +65,7 @@ public function __construct(
6265
* @param bool $a Whether to not crop the preview
6366
* @param bool $forceIcon Force returning an icon
6467
* @param string $mode How to crop the image
68+
* @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
6569
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
6670
*
6771
* 200: Preview returned
@@ -75,7 +79,8 @@ public function getPreview(
7579
int $y = 32,
7680
bool $a = false,
7781
bool $forceIcon = true,
78-
string $mode = 'fill'): Http\Response {
82+
string $mode = 'fill',
83+
bool $mimeFallback): Http\Response {
7984
if ($file === '' || $x === 0 || $y === 0) {
8085
return new DataResponse([], Http::STATUS_BAD_REQUEST);
8186
}
@@ -87,7 +92,7 @@ public function getPreview(
8792
return new DataResponse([], Http::STATUS_NOT_FOUND);
8893
}
8994

90-
return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode);
95+
return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
9196
}
9297

9398
/**
@@ -102,6 +107,7 @@ public function getPreview(
102107
* @param bool $a Whether to not crop the preview
103108
* @param bool $forceIcon Force returning an icon
104109
* @param string $mode How to crop the image
110+
* @param bool $mimeFallback Whether to fallback to the mime icon if no preview is available
105111
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
106112
*
107113
* 200: Preview returned
@@ -115,7 +121,8 @@ public function getPreviewByFileId(
115121
int $y = 32,
116122
bool $a = false,
117123
bool $forceIcon = true,
118-
string $mode = 'fill') {
124+
string $mode = 'fill',
125+
bool $mimeFallback = false) {
119126
if ($fileId === -1 || $x === 0 || $y === 0) {
120127
return new DataResponse([], Http::STATUS_BAD_REQUEST);
121128
}
@@ -129,19 +136,20 @@ public function getPreviewByFileId(
129136

130137
$node = array_pop($nodes);
131138

132-
return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode);
139+
return $this->fetchPreview($node, $x, $y, $a, $forceIcon, $mode, $mimeFallback);
133140
}
134141

135142
/**
136-
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>
143+
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, array<empty>, array{}>|RedirectResponse<string>
137144
*/
138145
private function fetchPreview(
139146
Node $node,
140147
int $x,
141148
int $y,
142149
bool $a,
143150
bool $forceIcon,
144-
string $mode) : Http\Response {
151+
string $mode,
152+
bool $mimeFallback = false) : Http\Response {
145153
if (!($node instanceof File) || (!$forceIcon && !$this->preview->isAvailable($node))) {
146154
return new DataResponse([], Http::STATUS_NOT_FOUND);
147155
}
@@ -167,6 +175,13 @@ private function fetchPreview(
167175
$response->cacheFor(3600 * 24, false, true);
168176
return $response;
169177
} catch (NotFoundException $e) {
178+
// If we have no preview enabled, we can redirect to the mime icon if any
179+
if ($mimeFallback) {
180+
if ($url = $this->mimeIconProvider->getMimeIconUrl($node->getMimeType())) {
181+
return new RedirectResponse($url);
182+
}
183+
}
184+
170185
return new DataResponse([], Http::STATUS_NOT_FOUND);
171186
} catch (\InvalidArgumentException $e) {
172187
return new DataResponse([], Http::STATUS_BAD_REQUEST);

lib/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@
533533
'OCP\\OCS\\IDiscoveryService' => $baseDir . '/lib/public/OCS/IDiscoveryService.php',
534534
'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php',
535535
'OCP\\Preview\\BeforePreviewFetchedEvent' => $baseDir . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
536+
'OCP\\Preview\\IMimeIconProvider' => $baseDir . '/lib/public/Preview/IMimeIconProvider.php',
536537
'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php',
537538
'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php',
538539
'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php',
@@ -1479,6 +1480,7 @@
14791480
'OC\\Preview\\MSOffice2007' => $baseDir . '/lib/private/Preview/MSOffice2007.php',
14801481
'OC\\Preview\\MSOfficeDoc' => $baseDir . '/lib/private/Preview/MSOfficeDoc.php',
14811482
'OC\\Preview\\MarkDown' => $baseDir . '/lib/private/Preview/MarkDown.php',
1483+
'OC\\Preview\\MimeIconProvider' => $baseDir . '/lib/private/Preview/MimeIconProvider.php',
14821484
'OC\\Preview\\Movie' => $baseDir . '/lib/private/Preview/Movie.php',
14831485
'OC\\Preview\\Office' => $baseDir . '/lib/private/Preview/Office.php',
14841486
'OC\\Preview\\OpenDocument' => $baseDir . '/lib/private/Preview/OpenDocument.php',

lib/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
566566
'OCP\\OCS\\IDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCS/IDiscoveryService.php',
567567
'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php',
568568
'OCP\\Preview\\BeforePreviewFetchedEvent' => __DIR__ . '/../../..' . '/lib/public/Preview/BeforePreviewFetchedEvent.php',
569+
'OCP\\Preview\\IMimeIconProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IMimeIconProvider.php',
569570
'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php',
570571
'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php',
571572
'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php',
@@ -1512,6 +1513,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
15121513
'OC\\Preview\\MSOffice2007' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOffice2007.php',
15131514
'OC\\Preview\\MSOfficeDoc' => __DIR__ . '/../../..' . '/lib/private/Preview/MSOfficeDoc.php',
15141515
'OC\\Preview\\MarkDown' => __DIR__ . '/../../..' . '/lib/private/Preview/MarkDown.php',
1516+
'OC\\Preview\\MimeIconProvider' => __DIR__ . '/../../..' . '/lib/private/Preview/MimeIconProvider.php',
15151517
'OC\\Preview\\Movie' => __DIR__ . '/../../..' . '/lib/private/Preview/Movie.php',
15161518
'OC\\Preview\\Office' => __DIR__ . '/../../..' . '/lib/private/Preview/Office.php',
15171519
'OC\\Preview\\OpenDocument' => __DIR__ . '/../../..' . '/lib/private/Preview/OpenDocument.php',
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2023 John Molakvoæ <[email protected]>
4+
*
5+
* @author John Molakvoæ <[email protected]>
6+
*
7+
* @license AGPL-3.0-or-later
8+
*
9+
* This code is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License, version 3,
11+
* as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License, version 3,
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>
20+
*
21+
*/
22+
namespace OC\Preview;
23+
24+
use OCA\Theming\ThemingDefaults;
25+
use OCP\App\IAppManager;
26+
use OCP\Files\IMimeTypeDetector;
27+
use OCP\IConfig;
28+
use OCP\IURLGenerator;
29+
use OCP\Preview\IMimeIconProvider;
30+
31+
class MimeIconProvider implements IMimeIconProvider {
32+
33+
public function __construct(
34+
protected IMimeTypeDetector $mimetypeDetector,
35+
protected IConfig $config,
36+
protected IURLGenerator $urlGenerator,
37+
protected IAppManager $appManager,
38+
protected ThemingDefaults $themingDefaults,
39+
) {}
40+
41+
public function getMimeIconUrl(string $mime): string|null {
42+
if (!$mime) {
43+
return null;
44+
}
45+
46+
// Fetch all the aliases
47+
$aliases = $this->mimetypeDetector->getAllAliases();
48+
49+
// Remove comments
50+
$aliases = array_filter($aliases, static function ($key) {
51+
return !($key === '' || $key[0] === '_');
52+
}, ARRAY_FILTER_USE_KEY);
53+
54+
// Map all the aliases recursively
55+
foreach ($aliases as $alias => $value) {
56+
if ($alias === $mime) {
57+
$mime = $value;
58+
}
59+
}
60+
61+
$fileName = str_replace('/', '-', $mime);
62+
if ($url = $this->searchfileName($fileName)) {
63+
return $url;
64+
}
65+
66+
$mimeType = explode('/', $mime)[0];
67+
if ($url = $this->searchfileName($mimeType)) {
68+
return $url;
69+
}
70+
71+
return null;
72+
}
73+
74+
private function searchfileName(string $fileName): string|null {
75+
// If the file exists in the current enabled legacy
76+
// custom theme, let's return it
77+
$theme = $this->config->getSystemValue('theme', '');
78+
if (!empty($theme)) {
79+
$path = "/themes/$theme/core/img/filetypes/$fileName.svg";
80+
if (file_exists(\OC::$SERVERROOT . $path)) {
81+
return $this->urlGenerator->getAbsoluteURL($path);
82+
}
83+
}
84+
85+
// Previously, we used to pass thi through Theming
86+
// But it was only used to colour icons containing
87+
// 0082c9. Since with vue we moved to inline svg icons,
88+
// we can just use the default core icons.
89+
90+
// Finally, if the file exists in core, let's return it
91+
$path = "/core/img/filetypes/$fileName.svg";
92+
if (file_exists(\OC::$SERVERROOT . $path)) {
93+
return $this->urlGenerator->getAbsoluteURL($path);
94+
}
95+
96+
return null;
97+
}
98+
}

lib/private/Server.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
use OC\OCS\DiscoveryService;
128128
use OC\Preview\GeneratorHelper;
129129
use OC\Preview\IMagickSupport;
130+
use OC\Preview\MimeIconProvider;
130131
use OC\Remote\Api\ApiFactory;
131132
use OC\Remote\InstanceFactory;
132133
use OC\RichObjectStrings\Validator;
@@ -262,6 +263,7 @@
262263
use OCA\Files_External\Service\BackendService;
263264
use OCP\Profiler\IProfiler;
264265
use OC\Profiler\Profiler;
266+
use OCP\Preview\IMimeIconProvider;
265267

266268
/**
267269
* Class Server
@@ -337,6 +339,7 @@ public function __construct($webRoot, \OC\Config $config) {
337339
});
338340
/** @deprecated 19.0.0 */
339341
$this->registerDeprecatedAlias('PreviewManager', IPreview::class);
342+
$this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
340343

341344
$this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) {
342345
return new \OC\Preview\Watcher(

lib/public/Files/IMimeTypeDetector.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,10 @@ public function detectString($data);
8282
* @since 8.2.0
8383
*/
8484
public function mimeTypeIcon($mimeType);
85+
86+
/**
87+
* @return string[]
88+
* @since 28.0.0
89+
*/
90+
public function getAllAliases(): array;
8591
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2023 John Molakvoæ <[email protected]>
4+
*
5+
* @author John Molakvoæ <[email protected]>
6+
*
7+
* @license AGPL-3.0-or-later
8+
*
9+
* This code is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License, version 3,
11+
* as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU Affero General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Affero General Public License, version 3,
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>
20+
*
21+
*/
22+
namespace OCP\Preview;
23+
24+
/**
25+
* Interface IMimeIconProvider
26+
*
27+
* @since 28.0.0
28+
*/
29+
interface IMimeIconProvider {
30+
public function getMimeIconUrl(string $mime): string|null;
31+
}

0 commit comments

Comments
 (0)