Add support for multiple timeline paths
parent
d9afbbe710
commit
a6ef3ac9bf
|
@ -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;
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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