Add support for multiple timeline paths
parent
d9afbbe710
commit
a6ef3ac9bf
|
@ -114,36 +114,34 @@ class ApiBase extends Controller
|
|||
// Anything else needs a user
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
return null;
|
||||
throw new \Exception('User not logged in');
|
||||
}
|
||||
$uid = $user->getUID();
|
||||
|
||||
$folder = null;
|
||||
$folderPath = $this->request->getParam('folder');
|
||||
$forcedTimelinePath = $this->request->getParam('timelinePath');
|
||||
$userFolder = $this->rootFolder->getUserFolder($uid);
|
||||
|
||||
if (null !== $folderPath) {
|
||||
$folder = $userFolder->get($folderPath);
|
||||
} elseif (null !== $forcedTimelinePath) {
|
||||
$folder = $userFolder->get($forcedTimelinePath);
|
||||
} else {
|
||||
$configPath = Exif::removeExtraSlash(Exif::getPhotosPath($this->config, $uid));
|
||||
$folder = $userFolder->get($configPath);
|
||||
}
|
||||
try {
|
||||
if (null !== $folderPath) {
|
||||
$folder = $userFolder->get(Exif::removeExtraSlash($folderPath));
|
||||
$root->addFolder($folder);
|
||||
} else {
|
||||
$timelinePath = $this->request->getParam('timelinePath', Exif::getPhotosPath($this->config, $uid));
|
||||
$timelinePath = Exif::removeExtraSlash($timelinePath);
|
||||
|
||||
if (!$folder instanceof Folder) {
|
||||
throw new \Exception('Folder not found');
|
||||
}
|
||||
// Multiple timeline path support
|
||||
$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 readable');
|
||||
}
|
||||
|
||||
// Don't add mount points for folder view
|
||||
$root->addFolder($folder);
|
||||
if (null === $folderPath) {
|
||||
$root->addMountPoints();
|
||||
throw new \Exception("Folder not found: {$msg}");
|
||||
}
|
||||
|
||||
return $root;
|
||||
|
|
|
@ -242,7 +242,8 @@ trait TimelineQueryDays
|
|||
$tmp = $actualPath[1];
|
||||
$actualPath[1] = $actualPath[2];
|
||||
$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
|
||||
if (isset($row['path']) && !empty($row['path'])) {
|
||||
$rootId = $row['rootid'] ?: $defaultRootId;
|
||||
$basePath = $internalPaths[$rootId] ?: '#__#';
|
||||
$basePath = $internalPaths[$rootId] ?? '#__#';
|
||||
$davPath = $davPaths[$rootId] ?: '';
|
||||
|
||||
if (0 === strpos($row['path'], $basePath)) {
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace OCA\Memories\Db;
|
||||
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
|
||||
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
|
||||
$id = $folder->getId();
|
||||
$folderPath = $folder->getPath();
|
||||
$this->folders[$id] = $folder;
|
||||
$this->folderPaths[$id] = $folderPath;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,12 @@
|
|||
>
|
||||
{{ t("memories", "Square grid mode") }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<MultiPathSelectionModal
|
||||
ref="multiPathModal"
|
||||
:title="pathSelTitle"
|
||||
@close="saveTimelinePath"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -65,18 +71,24 @@ input[type="text"] {
|
|||
<script lang="ts">
|
||||
import { Component, Mixins } from "vue-property-decorator";
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
|
||||
import { getFilePickerBuilder } from "@nextcloud/dialogs";
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
|
||||
import { getFilePickerBuilder } from "@nextcloud/dialogs";
|
||||
import { NcCheckboxRadioSwitch } from "@nextcloud/vue";
|
||||
|
||||
import MultiPathSelectionModal from "./modal/MultiPathSelectionModal.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
NcCheckboxRadioSwitch,
|
||||
MultiPathSelectionModal,
|
||||
},
|
||||
})
|
||||
export default class Settings extends Mixins(UserConfig, GlobalMixin) {
|
||||
get pathSelTitle() {
|
||||
return this.t("memories", "Choose Timeline Paths");
|
||||
}
|
||||
|
||||
async chooseFolder(title: string, initial: string) {
|
||||
const picker = getFilePickerBuilder(title)
|
||||
.setMultiSelect(false)
|
||||
|
@ -91,11 +103,13 @@ export default class Settings extends Mixins(UserConfig, GlobalMixin) {
|
|||
}
|
||||
|
||||
async chooseTimelinePath() {
|
||||
let newPath = await this.chooseFolder(
|
||||
this.t("memories", "Choose the root of your timeline"),
|
||||
this.config_timelinePath
|
||||
);
|
||||
if (newPath === "") newPath = "/";
|
||||
(<any>this.$refs.multiPathModal).open(this.config_timelinePath.split(";"));
|
||||
}
|
||||
|
||||
async saveTimelinePath(paths: string[]) {
|
||||
if (!paths || !paths.length) return;
|
||||
|
||||
const newPath = paths.join(";");
|
||||
if (newPath !== this.config_timelinePath) {
|
||||
this.config_timelinePath = newPath;
|
||||
await this.updateSetting("timelinePath");
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue