Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions lib/Controller/PhotosController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
use OCA\Maps\Service\GeophotoService;
use OCA\Maps\Service\PhotofilesService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\DB\Exception;
use OCP\Files\Folder;
use OCP\Files\InvalidPathException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
Expand Down Expand Up @@ -114,14 +116,18 @@ public function getNonLocalizedPhotos(?int $myMapId = null, ?string $timezone =
public function placePhotos($paths, $lats, $lngs, bool $directory = false, $myMapId = null, bool $relative = false): DataResponse {
$userFolder = $this->root->getUserFolder($this->userId);
if (!is_null($myMapId) and $myMapId !== '') {
// forbid folder placement in my-maps
if ($directory === 'true') {
// forbid folder placement in my-maps
throw new NotPermittedException();
}
$folders = $userFolder->getById($myMapId);
$folder = array_shift($folders);

$folder = $userFolder->getFirstNodeById($myMapId);
if (!($folder instanceof Folder)) {
return new DataResponse(statusCode: Http::STATUS_BAD_REQUEST);
}

// photo's path is relative to this map's folder => get full path, don't copy
if ($relative === 'true') {
if ($relative) {
foreach ($paths as $key => $path) {
$photoFile = $folder->get($path);
$paths[$key] = $userFolder->getRelativePath($photoFile->getPath());
Expand Down
6 changes: 5 additions & 1 deletion lib/Service/PhotofilesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ private function setFilesCoords($userId, $paths, $lats, $lngs) {
if ($this->isPhoto($file) && $file->isUpdateable()) {
$lat = (count($lats) > $i) ? $lats[$i] : $lats[0];
$lng = (count($lngs) > $i) ? $lngs[$i] : $lngs[0];
$photo = $this->photoMapper->findByFileIdUserId($file->getId(), $userId);
try {
$photo = $this->photoMapper->findByFileIdUserId($file->getId(), $userId);
} catch (DoesNotExistException) {
$photo = null;
}
$done[] = [
'path' => preg_replace('/^files/', '', $file->getInternalPath()),
'lat' => $lat,
Expand Down
10 changes: 7 additions & 3 deletions src/network.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import axios from '@nextcloud/axios'
import { default as realAxios } from 'axios'
import { generateUrl } from '@nextcloud/router'
import {
showError,
} from '@nextcloud/dialogs'

export function saveOptionValues(optionValues, myMapId = null, token = null) {
const req = {
Expand Down Expand Up @@ -328,6 +325,13 @@ export async function getPhotoSuggestions(myMapId = null, token = null, timezone
return axios.get(url, conf)
}

/**
* @param {string[]} paths
* @param {number[]} lats
* @param {number[]} lngs
* @param {boolean} directory - Is the placed path a directory
* @param {number | null} myMapId - The myMapId
*/
export function placePhotos(paths, lats, lngs, directory = false, myMapId = null) {
const req = {
paths,
Expand Down
109 changes: 109 additions & 0 deletions src/utils/photoPicker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { DialogBuilder, FilePickerBuilder } from '@nextcloud/dialogs'
import { n, t } from '@nextcloud/l10n'
import { placePhotos } from '../network.js'

interface LotLong {
lat: number
lng: number
}

const dialogBuilder = new DialogBuilder(t('maps', 'What do you want to place'))

/**
* Place photos or a photo folder on a given map and location.
*
* @param latLong - The geo location where to place the photos
* @param myMapId - The map to place the photos
*/
export async function placeFileOrFolder(latLong: LotLong, myMapId: number) {
const { promise, resolve } = Promise.withResolvers<unknown>()
const dialog = dialogBuilder
.setButtons([
{
label: t('maps', 'Photo folder'),
callback() {
resolve(placeFolder(latLong, myMapId))
},
},
{
label: t('maps', 'Photo files'),
callback() {
resolve(placeFiles(latLong, myMapId))
},
variant: 'primary',
},
])
.build()

await dialog.show()
return promise
}

/**
* Callback to select and place a folder.
*
* @param latLong - The location where to place
* @param myMapId - The map to place photos to
*/
async function placeFolder(latLong: LotLong, myMapId: number) {
const filePickerBuilder = new FilePickerBuilder(t('maps', 'Choose directory of photos to place'))
const filePicker = filePickerBuilder.allowDirectories(true)
.setMimeTypeFilter(['httpd/unix-directory'])
.setButtonFactory((nodes) => [{
callback: () => {},
label: nodes.length === 1
? t('maps', 'Select {photo}', { photo: nodes[0].displayname }, { escape: false })
: (nodes.length === 0
? t('maps', 'Select folder')
: n('maps', 'Select %n folder', 'Select %n folders', nodes.length)
),
disabled: nodes.length === 0,
variant: 'primary',
}])
.setMultiSelect(false)
.build()

try {
const folder = await filePicker.pick()
return placePhotos([folder], [latLong.lat], [latLong.lng], true, myMapId)
} catch {
// cancelled picking
}
}

/**
* Callback to select and place on or multiple photo files.
*
* @param latLong - The location where to place
* @param myMapId - The map to place photos to
*/
async function placeFiles(latLong: LotLong, myMapId: number) {
const filePickerBuilder = new FilePickerBuilder(t('maps', 'Choose photos to place'))
const filePicker = filePickerBuilder
.setMimeTypeFilter(['image/jpeg', 'image/tiff'])
.setButtonFactory((nodes) => [{
callback: () => {},
label: nodes.length === 1
? t('maps', 'Select {photo}', { photo: nodes[0].displayname }, { escape: false })
: (nodes.length === 0
? t('maps', 'Select photo')
: n('maps', 'Select %n photo', 'Select %n photos', nodes.length)
),
disabled: nodes.length === 0,
variant: 'primary',
}])
.setMultiSelect(true)
.build()

try {
const nodes = await filePicker.pick()
return placePhotos(nodes, [latLong.lat], [latLong.lng], false, myMapId)
} catch {
// cancelled picking
}
}
56 changes: 12 additions & 44 deletions src/views/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ import { geoToLatLng, getFormattedADR } from '../utils/mapUtils.js'
import * as network from '../network.js'
import { all as axiosAll, spread as axiosSpread } from 'axios'
import { generateUrl } from '@nextcloud/router'
import { placeFileOrFolder } from '../utils/photoPicker.ts'

export default {
name: 'App',
Expand Down Expand Up @@ -946,50 +947,17 @@ export default {
console.error(error)
})
},
placePhotoFilesOrFolder(latlng) {
OC.dialogs.confirmDestructive(
'',
t('maps', 'What do you want to place?'),
{
type: OC.dialogs.YES_NO_BUTTONS,
confirm: t('maps', 'Photo files'),
confirmClasses: '',
cancel: t('maps', 'Photo folders'),
},
(result) => {
if (result) {
this.placePhotoFiles(latlng)
} else {
this.placePhotoFolder(latlng)
}
},
true,
)
},
placePhotoFiles(latlng) {
OC.dialogs.filepicker(
t('maps', 'Choose pictures to place'),
(targetPath) => {
this.placePhotos(targetPath, [latlng.lat], [latlng.lng])
},
true,
['image/jpeg', 'image/tiff'],
true,
)
},
placePhotoFolder(latlng) {
OC.dialogs.filepicker(
t('maps', 'Choose directory of pictures to place'),
(targetPath) => {
if (targetPath === '') {
targetPath = '/'
}
this.placePhotos([targetPath], [latlng.lat], [latlng.lng], true)
},
false,
'httpd/unix-directory',
true,
)
async placePhotoFilesOrFolder(latLong) {
try {
const response = await placeFileOrFolder(latLong, this.myMapId)
this.getPhotos()
this.saveAction({
type: 'photoMove',
content: response.data,
})
} catch (error) {
console.error(error)
}
},
placePhotos(paths, lats, lngs, directory = false, save = true, reload = true) {
network.placePhotos(paths, lats, lngs, directory, this.myMapId).then((response) => {
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"extends": "@vue/tsconfig",
"compilerOptions": {
"allowJs": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"noEmit": false,
"outDir": "js",
},
"vueCompilerOptions": {
"target": 2.7
Expand Down
Loading