Skip to content
Merged
Next Next commit
Add config to select source directories
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Feb 15, 2024
commit e038644eb1691a48fd1a553accb64cbeecebb2fe
1 change: 1 addition & 0 deletions lib/Service/UserConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class UserConfigService {
public const DEFAULT_CONFIGS = [
'croppedLayout' => 'false',
'photosLocation' => '/Photos',
'photosSourceFolders' => '["/Photos"]',
];

private IConfig $config;
Expand Down
114 changes: 114 additions & 0 deletions src/components/Settings/PhotosFolder.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<template>
<div class="folder">
<FolderMultiple v-if="path === '/'" />
<Folder v-else />
<span class="folder__info">
<div class="folder__path">{{ folderName }}</div>
<div v-if="subname !== ''">
{{ subname }}
</div>
</span>
<NcButton v-if="canDelete"
type="tertiary"
:aria-label="t('photos', 'Delete source directory')"
@click="emitRemoveSourceFolder">
<template #icon>
<Close :size="20" />
</template>
</NcButton>
</div>
</template>

<script>
import { defineComponent } from 'vue'

import Folder from 'vue-material-design-icons/Folder.vue'
import FolderMultiple from 'vue-material-design-icons/FolderMultiple.vue'
import Close from 'vue-material-design-icons/Close.vue'

import { NcButton } from '@nextcloud/vue'
import { translate as t } from '@nextcloud/l10n'

export default defineComponent({
name: 'PhotosFolder',

components: {
NcButton,
Folder,
FolderMultiple,
Close,
},

props: {
path: {
type: String,
required: true,
},
canDelete: {
type: Boolean,
default: false,
},
},

emits: ['remove-folder'],

computed: {
folderName() {
if (this.path === '/') {
return t('photos', 'All folders')
} else {
return this.path.split('/').pop()
}
},

/**
* Return the summary path of the folder
* Examples:
* - / ==> Home
* - /a ==> nothing
* - /a/b ==> /a
* - /a/b/c ==> /a/b
* - /a/b/c/d ==> /a/b
*/
subname() {
const slashesCount = (this.path.match(/\//g) ?? []).length

switch (slashesCount) {
case 1:
return ''
case 2:
return this.path.split('/').splice(0, 2).join('/')
default:
return this.path.split('/').splice(0, 3).join('/')
}
Comment on lines +79 to +89
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this sorcery? 😁

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To properly display the path of the folder. Let me add a comment.

Full path Displayed path
/ Home
/a nothing
/a/b /a
/a/b/c /a/b

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More comments the better :)

},
},

methods: {
emitRemoveSourceFolder() {
this.$emit('remove-folder')
},

t,
},
})
</script>

<style lang="scss" scoped>
.folder {
display: flex;
gap: 16px;
min-width: 300px;

&__info {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
}

&__path {
font-weight: bold;
}
}
</style>
114 changes: 114 additions & 0 deletions src/components/Settings/PhotosSourceLocationsSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!--
- @copyright Copyright (c) 2019 John Molakvoæ <[email protected]>
-
- @author John Molakvoæ <[email protected]>
-
- @license AGPL-3.0-or-later
-
- 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 class="photos-locations">
<ul>
<li v-for="(source, index) in photosSourceFolders"
:key="index">
<PhotosFolder :path="source" :can-delete="photosSourceFolders.length !== 1" @remove-folder="removeSourceFolder(index)" />
</li>
</ul>

<NcButton :aria-label="t('photos', 'Add source directory')"
@click="debounceAddSourceFolder">
<template #icon>
<Plus :size="20" />
</template>
{{ t('photos', 'Add folder') }}
</NcButton>
</div>
</template>

<script>
import debounce from 'debounce'
import { defineComponent } from 'vue'

import Plus from 'vue-material-design-icons/Plus.vue'

import { NcButton } from '@nextcloud/vue'
import { getFilePickerBuilder } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'

import UserConfig from '../../mixins/UserConfig.js'
import PhotosFolder from './PhotosFolder.vue'

export default defineComponent({
name: 'PhotosSourceLocationsSettings',

components: {
NcButton,
Plus,
PhotosFolder,
},

mixins: [
UserConfig,
],

methods: {
debounceAddSourceFolder: debounce(function(...args) {
this.addSourceFolder(...args)
}, 200, false),

async openFilePicker(title) {
const picker = getFilePickerBuilder(title)
.setMultiSelect(false)
.setModal(true)
.setType(1)
.addMimeTypeFilter('httpd/unix-directory')
.allowDirectories()
.build()

return picker.pick()
},

async addSourceFolder() {
const pickedFolder = await this.openFilePicker(t('photos', 'Select a source folder for your media'))
if (this.photosSourceFolders.includes(pickedFolder)) {
return
}
this.photosSourceFolders.push(pickedFolder)
this.updateSetting('photosSourceFolders')
},

removeSourceFolder(index) {
this.photosSourceFolders.splice(index, 1)
this.updateSetting('photosSourceFolders')
},

t,
},
})
</script>

<style lang="scss" scoped>
.photos-locations {
display: flex;
flex-direction: column;
width: fit-content;

ul {
margin-bottom: 16px;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,32 @@

<template>
<div class="photos-location">
<NcTextField class="photos-location__text-field"
:label="t('photos', 'Default Photos upload and Albums location')"
:value.sync="photosLocation"
@update:value="debounceUpdatePhotosFolder(photosLocation)" />
<PhotosFolder :path="photosLocation" />

<NcButton :aria-label="t('photos', 'Choose default Photos upload and Albums location')"
@click="debounceSelectPhotosFolder">
<template #icon>
<Folder :size="20" />
</template>
{{ t('photos', 'Choose a different folder') }}
</NcButton>
</div>
</template>

<script>
import debounce from 'debounce'
import { defineComponent } from 'vue'

import { NcButton, NcTextField } from '@nextcloud/vue'
import Folder from 'vue-material-design-icons/Folder.vue'

import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import { NcButton } from '@nextcloud/vue'
import { getFilePickerBuilder } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'

import UserConfig from '../../mixins/UserConfig.js'
import PhotosFolder from './PhotosFolder.vue'

export default {
name: 'PhotosLocationSettings',
export default defineComponent({
name: 'PhotosUploadLocationSettings',

components: {
NcButton,
NcTextField,
Folder,
PhotosFolder,
},

mixins: [
Expand All @@ -63,8 +59,13 @@ export default {
this.selectPhotosFolder()
}),

selectPhotosFolder() {
const picker = getFilePickerBuilder(t('photos', 'Select the default location for your media'))
async selectPhotosFolder() {
const pickedFolder = await this.openFilePicker(t('photos', 'Select the default upload location for your media'))
this.updatePhotosFolder(pickedFolder)
},

async openFilePicker(title) {
const picker = getFilePickerBuilder(title)
.setMultiSelect(false)
.setModal(true)
.setType(1)
Expand All @@ -73,47 +74,27 @@ export default {
.startAt(this.photosLocation)
.build()

picker.pick()
.then(this.updatePhotosFolder)
return picker.pick()
},

debounceUpdatePhotosFolder: debounce(function(...args) {
this.updatePhotosFolder(...args)
}, 300),

updatePhotosFolder(path) {
console.debug(`Path '${path}' selected for photos location`)
if (typeof path !== 'string' || path.trim() === '' || !path.startsWith('/')) {
showError(t('photos', 'Invalid location selected'))
return
}

if (path.includes('//')) {
path = path.replace(/\/\//gi, '/')
}

this.photosLocation = path
this.updateSetting('photosLocation')
},

t,
},
}
})
</script>

<style lang="scss" scoped>
.photos-location {
display: flex;
align-items: flex-end;
gap: 0 8px;

&__text-field {
max-width: 300px;

:deep {
.input-field__main-wrapper,
input {
height: var(--default-clickable-area) !important;
}
}
flex-direction: column;
width: fit-content;

.folder {
margin-bottom: 16px;
}
}
</style>
Loading