Add support for multiple timeline paths

pull/221/head
Varun Patil 2022-11-16 00:16:01 -08:00
parent d9afbbe710
commit a6ef3ac9bf
5 changed files with 174 additions and 32 deletions

View File

@ -114,36 +114,34 @@ class ApiBase extends Controller
// Anything else needs a user // Anything else needs a user
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
if (null === $user) { if (null === $user) {
return null; throw new \Exception('User not logged in');
} }
$uid = $user->getUID(); $uid = $user->getUID();
$folder = null; $folder = null;
$folderPath = $this->request->getParam('folder'); $folderPath = $this->request->getParam('folder');
$forcedTimelinePath = $this->request->getParam('timelinePath');
$userFolder = $this->rootFolder->getUserFolder($uid); $userFolder = $this->rootFolder->getUserFolder($uid);
if (null !== $folderPath) { try {
$folder = $userFolder->get($folderPath); if (null !== $folderPath) {
} elseif (null !== $forcedTimelinePath) { $folder = $userFolder->get(Exif::removeExtraSlash($folderPath));
$folder = $userFolder->get($forcedTimelinePath); $root->addFolder($folder);
} else { } else {
$configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid)); $timelinePath = $this->request->getParam('timelinePath', Exif::getPhotosPath($this->config, $uid));
$folder = $userFolder->get($configPath); $timelinePath = Exif::removeExtraSlash($timelinePath);
}
if (!$folder instanceof Folder) { // Multiple timeline path support
throw new \Exception('Folder not found'); $paths = explode(';', $timelinePath);
} foreach ($paths as &$path) {
$folder = $userFolder->get(trim($path));
$root->addFolder($folder);
}
$root->addMountPoints();
}
} catch (\OCP\Files\NotFoundException $e) {
$msg = $e->getMessage();
if (!($folder->getPermissions() & \OCP\Constants::PERMISSION_READ)) { throw new \Exception("Folder not found: {$msg}");
throw new \Exception('Folder not readable');
}
// Don't add mount points for folder view
$root->addFolder($folder);
if (null === $folderPath) {
$root->addMountPoints();
} }
return $root; return $root;

View File

@ -242,7 +242,8 @@ trait TimelineQueryDays
$tmp = $actualPath[1]; $tmp = $actualPath[1];
$actualPath[1] = $actualPath[2]; $actualPath[1] = $actualPath[2];
$actualPath[2] = $tmp; $actualPath[2] = $tmp;
$davPaths[$fileid] = implode('/', $actualPath); $davPath = implode('/', $actualPath);
$davPaths[$fileid] = \OCA\Memories\Exif::removeExtraSlash('/'.$davPath.'/');
} }
} }
} }
@ -270,7 +271,7 @@ trait TimelineQueryDays
// Check if path exists and starts with basePath and remove // Check if path exists and starts with basePath and remove
if (isset($row['path']) && !empty($row['path'])) { if (isset($row['path']) && !empty($row['path'])) {
$rootId = $row['rootid'] ?: $defaultRootId; $rootId = $row['rootid'] ?: $defaultRootId;
$basePath = $internalPaths[$rootId] ?: '#__#'; $basePath = $internalPaths[$rootId] ?? '#__#';
$davPath = $davPaths[$rootId] ?: ''; $davPath = $davPaths[$rootId] ?: '';
if (0 === strpos($row['path'], $basePath)) { if (0 === strpos($row['path'], $basePath)) {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace OCA\Memories\Db; namespace OCA\Memories\Db;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\Files\Node;
class TimelineRoot class TimelineRoot
{ {
@ -16,11 +17,27 @@ class TimelineRoot
{ {
} }
public function addFolder(Folder &$folder) /**
* Add a folder to the root.
*
* @param Node $folder Node to add
*
* @throws \Exception if node is not valid readable folder
*/
public function addFolder(Node &$folder)
{ {
$folderPath = $folder->getPath();
if (!$folder instanceof Folder) {
throw new \Exception("Not a folder: {$folderPath}");
}
if (!($folder->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
throw new \Exception("Folder not readable: {$folderPath}");
}
// Add top level folder // Add top level folder
$id = $folder->getId(); $id = $folder->getId();
$folderPath = $folder->getPath();
$this->folders[$id] = $folder; $this->folders[$id] = $folder;
$this->folderPaths[$id] = $folderPath; $this->folderPaths[$id] = $folderPath;
} }

View File

@ -53,6 +53,12 @@
> >
{{ t("memories", "Square grid mode") }} {{ t("memories", "Square grid mode") }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<MultiPathSelectionModal
ref="multiPathModal"
:title="pathSelTitle"
@close="saveTimelinePath"
/>
</div> </div>
</template> </template>
@ -65,18 +71,24 @@ input[type="text"] {
<script lang="ts"> <script lang="ts">
import { Component, Mixins } from "vue-property-decorator"; import { Component, Mixins } from "vue-property-decorator";
import GlobalMixin from "../mixins/GlobalMixin"; import GlobalMixin from "../mixins/GlobalMixin";
import { getFilePickerBuilder } from "@nextcloud/dialogs";
import UserConfig from "../mixins/UserConfig"; import UserConfig from "../mixins/UserConfig";
import { getFilePickerBuilder } from "@nextcloud/dialogs";
import { NcCheckboxRadioSwitch } from "@nextcloud/vue"; import { NcCheckboxRadioSwitch } from "@nextcloud/vue";
import MultiPathSelectionModal from "./modal/MultiPathSelectionModal.vue";
@Component({ @Component({
components: { components: {
NcCheckboxRadioSwitch, NcCheckboxRadioSwitch,
MultiPathSelectionModal,
}, },
}) })
export default class Settings extends Mixins(UserConfig, GlobalMixin) { export default class Settings extends Mixins(UserConfig, GlobalMixin) {
get pathSelTitle() {
return this.t("memories", "Choose Timeline Paths");
}
async chooseFolder(title: string, initial: string) { async chooseFolder(title: string, initial: string) {
const picker = getFilePickerBuilder(title) const picker = getFilePickerBuilder(title)
.setMultiSelect(false) .setMultiSelect(false)
@ -91,11 +103,13 @@ export default class Settings extends Mixins(UserConfig, GlobalMixin) {
} }
async chooseTimelinePath() { async chooseTimelinePath() {
let newPath = await this.chooseFolder( (<any>this.$refs.multiPathModal).open(this.config_timelinePath.split(";"));
this.t("memories", "Choose the root of your timeline"), }
this.config_timelinePath
); async saveTimelinePath(paths: string[]) {
if (newPath === "") newPath = "/"; if (!paths || !paths.length) return;
const newPath = paths.join(";");
if (newPath !== this.config_timelinePath) { if (newPath !== this.config_timelinePath) {
this.config_timelinePath = newPath; this.config_timelinePath = newPath;
await this.updateSetting("timelinePath"); await this.updateSetting("timelinePath");

View File

@ -0,0 +1,112 @@
<template>
<Modal @close="close" v-if="show" size="small">
<template #title>
{{ title }}
</template>
<ul>
<li v-for="(path, index) in paths" :key="index" class="path">
{{ path }}
<NcActions :inline="1">
<NcActionButton
:aria-label="t('memories', 'Remove')"
@click="remove(index)"
>
{{ t("memories", "Remove") }}
<template #icon> <CloseIcon :size="20" /> </template>
</NcActionButton>
</NcActions>
</li>
</ul>
<template #buttons>
<NcButton @click="add" class="button" type="secondary">
{{ t("memories", "Add Path") }}
</NcButton>
<NcButton @click="save" class="button" type="primary">
{{ t("memories", "Save") }}
</NcButton>
</template>
</Modal>
</template>
<script lang="ts">
import { Component, Emit, Mixins, Prop } from "vue-property-decorator";
import GlobalMixin from "../../mixins/GlobalMixin";
import UserConfig from "../../mixins/UserConfig";
import Modal from "./Modal.vue";
import { getFilePickerBuilder } from "@nextcloud/dialogs";
import { NcActions, NcActionButton, NcButton } from "@nextcloud/vue";
import CloseIcon from "vue-material-design-icons/Close.vue";
@Component({
components: {
Modal,
NcActions,
NcActionButton,
NcButton,
CloseIcon,
},
})
export default class Settings extends Mixins(UserConfig, GlobalMixin) {
@Prop({ required: true }) title: string;
private show = false;
private paths: string[] = [];
@Emit("close")
public close(list: string[]) {
this.show = false;
}
public open(paths: string[]) {
this.paths = paths;
this.show = true;
}
public save() {
this.close(this.paths);
}
async chooseFolder(title: string, initial: string) {
const picker = getFilePickerBuilder(title)
.setMultiSelect(false)
.setModal(true)
.setType(1)
.addMimeTypeFilter("httpd/unix-directory")
.allowDirectories()
.startAt(initial)
.build();
return await picker.pick();
}
public async add() {
let newPath = await this.chooseFolder(
this.t("memories", "Add a root to your timeline"),
"/"
);
if (newPath === "") newPath = "/";
this.paths.push(newPath);
}
public remove(index: number) {
this.paths.splice(index, 1);
}
}
</script>
<style lang="scss" scoped>
.path {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.1rem;
padding-left: 10px;
word-wrap: break-all;
}
</style>