Skip to content

Commit

Permalink
Unified search UI
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Jul 29, 2020
1 parent 1f42a8b commit 4116020
Show file tree
Hide file tree
Showing 40 changed files with 1,131 additions and 93 deletions.
31 changes: 22 additions & 9 deletions apps/comments/lib/Search/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
Expand All @@ -36,6 +37,9 @@

class Provider implements IProvider {

/** @var IUserManager */
private $userManager;

/** @var IL10N */
private $l10n;

Expand All @@ -45,9 +49,11 @@ class Provider implements IProvider {
/** @var LegacyProvider */
private $legacyProvider;

public function __construct(IL10N $l10n,
public function __construct(IUserManager $userManager,
IL10N $l10n,
IURLGenerator $urlGenerator,
LegacyProvider $legacyProvider) {
$this->userManager = $userManager;
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
$this->legacyProvider = $legacyProvider;
Expand All @@ -57,23 +63,30 @@ public function getId(): string {
return 'comments';
}

public function getName(): string {
return $this->l10n->t('Comments');
}

public function search(IUser $user, ISearchQuery $query): SearchResult {
return SearchResult::complete(
$this->l10n->t('Comments'),
array_map(function (Result $result) {
$path = $result->path;
$pathInfo = pathinfo($path);
$isUser = $this->userManager->userExists($result->authorId);
$avatarUrl = $isUser
? $this->urlGenerator->linkToRoute('core.avatar.getAvatar', ['userId' => $result->authorId, 'size' => 42])
: $this->urlGenerator->linkToRoute('core.GuestAvatar.getAvatar', ['guestName' => $result->authorId, 'size' => 42]);
return new CommentsSearchResultEntry(
$this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]),
$avatarUrl,
$result->name,
$path,
$this->urlGenerator->linkToRoute(
'files.view.index',
[
'dir' => $pathInfo['dirname'],
'scrollto' => $pathInfo['basename'],
]
)
$this->urlGenerator->linkToRoute('files.view.index',[
'dir' => $pathInfo['dirname'],
'scrollto' => $pathInfo['basename'],
]),
'',
true
);
}, $this->legacyProvider->search($query->getTerm()))
);
Expand Down
32 changes: 29 additions & 3 deletions apps/files/lib/Search/FilesSearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,43 @@ public function getId(): string {
return 'files';
}

public function getName(): string {
return $this->l10n->t('Files');
}

public function search(IUser $user, ISearchQuery $query): SearchResult {
return SearchResult::complete(
$this->l10n->t('Files'),
array_map(function (FileResult $result) {
// Generate thumbnail url
$thumbnailUrl = $result->type === 'folder'
? ''
: $this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]);

return new FilesSearchResultEntry(
$this->urlGenerator->linkToRoute('core.Preview.getPreviewByFileId', ['x' => 32, 'y' => 32, 'fileId' => $result->id]),
$thumbnailUrl,
$result->name,
$result->path,
$result->link
$this->formatSubline($result),
$result->link,
$result->type === 'folder' ? 'icon-folder' : 'icon-filetype-file'
);
}, $this->fileSearch->search($query->getTerm()))
);
}

/**
* Format subline for files
*
* @param FileResult $result
* @return string
*/
private function formatSubline($result): string {
// Do not show the location if the file is in root
if ($result->path === '/' . $result->name) {
return '';
}

$path = ltrim(dirname($result->path), '/');
return $this->l10n->t('in %s', [$path]);
}
}
5 changes: 3 additions & 2 deletions apps/files/lib/Search/FilesSearchResultEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class FilesSearchResultEntry extends ASearchResultEntry {
public function __construct(string $thumbnailUrl,
string $filename,
string $path,
string $url) {
parent::__construct($thumbnailUrl, $filename, $path, $url);
string $url,
string $icon) {
parent::__construct($thumbnailUrl, $filename, $path, $url, $icon, false);
}
}
2 changes: 1 addition & 1 deletion core/Controller/UnifiedSearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public function search(string $providerId,
?int $sortOrder = null,
?int $limit = null,
$cursor = null): JSONResponse {
if (empty($term)) {
if (empty(trim($term))) {
return new JSONResponse(null, Http::STATUS_BAD_REQUEST);
}

Expand Down
2 changes: 2 additions & 0 deletions core/css/css-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@

--animation-quick: $animation-quick;
--animation-slow: $animation-slow;

--header-height: $header-height;
}
8 changes: 4 additions & 4 deletions core/css/icons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -384,20 +384,20 @@ audio, canvas, embed, iframe, img, input, object, video {

.icon-file,
.icon-filetype-text {
@include icon-color('text', 'filetypes', $color-black, 1, true);
@include icon-color('text', 'filetypes', #969696, 1, true);
}

.icon-filetype-file {
@include icon-color('file', 'filetypes', $color-black, 1, true);
@include icon-color('file', 'filetypes', #969696, 1, true);
}

@include icon-black-white('folder', 'filetypes', 1, true);
.icon-filetype-folder {
@include icon-color('folder', 'filetypes', $color-black, 1, true);
@include icon-color('folder', 'filetypes', $color-primary, 1, true);
}

.icon-filetype-folder-drag-accept {
@include icon-color('folder-drag-accept', 'filetypes', $color-black, 1, true);
@include icon-color('folder-drag-accept', 'filetypes', $color-primary, 1, true);
}


Expand Down
2 changes: 1 addition & 1 deletion core/js/dist/files_client.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/files_client.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/files_fileinfo.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/files_fileinfo.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/files_iedavclient.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/files_iedavclient.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/install.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/install.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/login.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/login.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/main.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/maintenance.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/maintenance.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/recommendedapps.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/js/dist/recommendedapps.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions core/js/dist/unified-search.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/js/dist/unified-search.js.map

Large diffs are not rendered by default.

206 changes: 206 additions & 0 deletions core/src/components/HeaderMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- 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/>.
-
-->
<template>
<div v-click-outside="closeMenu" :class="{ 'header-menu--opened': opened }" class="header-menu">
<a class="header-menu__trigger"
href="#"
:aria-controls="`header-menu-${id}`"
:aria-expanded="opened"
aria-haspopup="true"
@click.prevent="toggleMenu">
<slot name="trigger" />
</a>
<div v-if="opened"
:id="`header-menu-${id}`"
class="header-menu__wrapper"
role="menu">
<div class="header-menu__carret" />
<div class="header-menu__content">
<slot />
</div>
</div>
</div>
</template>

<script>
import { directive as ClickOutside } from 'v-click-outside'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'

export default {
name: 'HeaderMenu',

directives: {
ClickOutside,
},

props: {
id: {
type: String,
required: true,
},
open: {
type: Boolean,
default: false,
},
},

data() {
return {
opened: this.open,
}
},

watch: {
open(newVal) {
this.opened = newVal
this.$nextTick(() => {
if (this.opened) {
this.openMenu()
} else {
this.closeMenu()
}
})
},
},

mounted() {
document.addEventListener('keydown', this.onKeyDown)
},

beforeMount() {
subscribe(`header-menu-${this.id}-close`, this.closeMenu)
subscribe(`header-menu-${this.id}-open`, this.openMenu)
},

beforeDestroy() {
unsubscribe(`header-menu-${this.id}-close`, this.closeMenu)
unsubscribe(`header-menu-${this.id}-open`, this.openMenu)
},

methods: {
/**
* Toggle the current menu open state
*/
toggleMenu() {
// Toggling current state
if (!this.opened) {
this.openMenu()
} else {
this.closeMenu()
}
},

/**
* Close the current menu
*/
closeMenu() {
if (!this.opened) {
return
}

this.opened = false
this.$emit('close')
this.$emit('update:open', false)
emit(`header-menu-${this.id}-close`)
},

/**
* Open the current menu
*/
openMenu() {
if (this.opened) {
return
}

this.opened = true
this.$emit('open')
this.$emit('update:open', true)
emit(`header-menu-${this.id}-open`)
},

onKeyDown(event) {
// If opened and escape pressed, close
if (event.key === 'Escape' && this.opened) {
event.preventDefault()
this.closeMenu()
}
},
},
}
</script>

<style lang="scss" scoped>
.header-menu {
&__trigger {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 100%;
margin: 0;
padding: 0;
cursor: pointer;
opacity: .6;
}

&--opened &__trigger,
&__trigger:hover,
&__trigger:focus,
&__trigger:active {
opacity: 1;
}

&__wrapper {
position: absolute;
z-index: 2000;
top: 50px;
right: 5px;
box-sizing: border-box;
margin: 0;
border-radius: 0 0 var(--border-radius) var(--border-radius);
background-color: var(--color-main-background);

filter: drop-shadow(0 1px 5px var(--color-box-shadow));
}

&__carret {
position: absolute;
right: 10px;
bottom: 100%;
width: 0;
height: 0;
content: ' ';
pointer-events: none;
border: 10px solid transparent;
border-bottom-color: var(--color-main-background);
}

&__content {
overflow: auto;
width: 350px;
max-width: 350px;
min-height: calc(44px * 1.5);
max-height: calc(100vh - 50px * 2);
}
}

</style>
Loading

0 comments on commit 4116020

Please sign in to comment.