places: implement hierarchy (close #511)
Signed-off-by: Varun Patil <radialapps@gmail.com>pull/672/head
parent
f89da9e541
commit
34d48c3cc6
|
@ -4,9 +4,12 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [v5.1.1] - Unreleased
|
## [v5.1.1] - Unreleased
|
||||||
|
|
||||||
|
**Note:** You will need to run `occ memories:places-setup --recalculate` to re-index places (or reindex everything)
|
||||||
|
|
||||||
- New project home page: https://memories.gallery
|
- New project home page: https://memories.gallery
|
||||||
- New Discord community: https://discord.gg/7Dr9f9vNjJ
|
- New Discord community: https://discord.gg/7Dr9f9vNjJ
|
||||||
- Nextcloud 27 compatibility
|
- Nextcloud 27 compatibility
|
||||||
|
- **Feature**: Hierarchical places view
|
||||||
- **Feature**: Layout improvements especially for mobile.
|
- **Feature**: Layout improvements especially for mobile.
|
||||||
- **Feature**: Allow downloading entire publicly shared albums.
|
- **Feature**: Allow downloading entire publicly shared albums.
|
||||||
- **Feature**: Basic preview generation configuration in admin interface.
|
- **Feature**: Basic preview generation configuration in admin interface.
|
||||||
|
|
|
@ -65,6 +65,9 @@ class PlacesBackend extends Backend
|
||||||
|
|
||||||
public function getClusters(): array
|
public function getClusters(): array
|
||||||
{
|
{
|
||||||
|
$inside = (int) $this->request->getParam('inside', 0);
|
||||||
|
$marked = (int) $this->request->getParam('mark', 1);
|
||||||
|
|
||||||
$query = $this->tq->getBuilder();
|
$query = $this->tq->getBuilder();
|
||||||
|
|
||||||
// SELECT location name and count of photos
|
// SELECT location name and count of photos
|
||||||
|
@ -75,7 +78,41 @@ class PlacesBackend extends Backend
|
||||||
$query->where($query->expr()->gt('e.admin_level', $query->expr()->literal(0, \PDO::PARAM_INT)));
|
$query->where($query->expr()->gt('e.admin_level', $query->expr()->literal(0, \PDO::PARAM_INT)));
|
||||||
|
|
||||||
// WHERE there are items with this osm_id
|
// WHERE there are items with this osm_id
|
||||||
$query->innerJoin('e', 'memories_places', 'mp', $query->expr()->eq('mp.osm_id', 'e.osm_id'));
|
$mpJoinOn = [$query->expr()->eq('mp.osm_id', 'e.osm_id')];
|
||||||
|
|
||||||
|
// AND these items are inside the requested place
|
||||||
|
if ($inside > 0) {
|
||||||
|
$sub = $this->tq->getBuilder();
|
||||||
|
$sub->select($query->expr()->literal(1))->from('memories_places', 'mp_sq')
|
||||||
|
->where($sub->expr()->eq('mp_sq.osm_id', $query->createNamedParameter($inside, \PDO::PARAM_INT)))
|
||||||
|
->andWhere($sub->expr()->eq('mp_sq.fileid', 'mp.fileid'))
|
||||||
|
;
|
||||||
|
$mpJoinOn[] = $query->createFunction("EXISTS ({$sub->getSQL()})");
|
||||||
|
|
||||||
|
// Add WHERE clauses to main query to filter out admin_levels
|
||||||
|
$sub = $this->tq->getBuilder();
|
||||||
|
$sub->select('e_sq.admin_level')
|
||||||
|
->from('memories_planet', 'e_sq')
|
||||||
|
->where($sub->expr()->eq('e_sq.osm_id', $query->createNamedParameter($inside, \PDO::PARAM_INT)))
|
||||||
|
;
|
||||||
|
$adminSql = "({$sub->getSQL()})";
|
||||||
|
$query->andWhere($query->expr()->gt('e.admin_level', $query->createFunction($adminSql)))
|
||||||
|
->andWhere($query->expr()->lte('e.admin_level', $query->createFunction("{$adminSql} + 3")))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else if we are looking for countries
|
||||||
|
elseif ($inside === -1) {
|
||||||
|
$query->where($query->expr()->eq('e.admin_level', $query->expr()->literal(2, \PDO::PARAM_INT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND these items are marked (only if not inside)
|
||||||
|
elseif ($marked > 0) {
|
||||||
|
$mpJoinOn[] = $query->expr()->eq('mp.mark', $query->expr()->literal(1, \PDO::PARAM_INT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// JOIN on memories_places
|
||||||
|
$query->innerJoin('e', 'memories_places', 'mp', $query->expr()->andX(...$mpJoinOn));
|
||||||
|
|
||||||
// WHERE these items are memories indexed photos
|
// WHERE these items are memories indexed photos
|
||||||
$query->innerJoin('mp', 'memories', 'm', $query->expr()->eq('m.fileid', 'mp.fileid'));
|
$query->innerJoin('mp', 'memories', 'm', $query->expr()->eq('m.fileid', 'mp.fileid'));
|
||||||
|
@ -104,8 +141,14 @@ class PlacesBackend extends Backend
|
||||||
// INNER JOIN back on the planet table to get the names
|
// INNER JOIN back on the planet table to get the names
|
||||||
$query->innerJoin('sub', 'memories_planet', 'e', $query->expr()->eq('e.osm_id', 'sub.osm_id'));
|
$query->innerJoin('sub', 'memories_planet', 'e', $query->expr()->eq('e.osm_id', 'sub.osm_id'));
|
||||||
|
|
||||||
|
// WHERE at least 3 photos if want marked clusters
|
||||||
|
if ($marked) {
|
||||||
|
$query->andWhere($query->expr()->gte('sub.count', $query->expr()->literal(3, \PDO::PARAM_INT)));
|
||||||
|
}
|
||||||
|
|
||||||
// ORDER BY name and osm_id
|
// ORDER BY name and osm_id
|
||||||
$query->orderBy($query->createFunction('LOWER(e.name)'), 'ASC');
|
$query->orderBy($query->createFunction('sub.count'), 'DESC');
|
||||||
|
$query->addOrderBy('e.name');
|
||||||
$query->addOrderBy('e.osm_id'); // tie-breaker
|
$query->addOrderBy('e.osm_id'); // tie-breaker
|
||||||
|
|
||||||
// FETCH all tags
|
// FETCH all tags
|
||||||
|
@ -115,7 +158,7 @@ class PlacesBackend extends Backend
|
||||||
$lang = Util::getUserLang();
|
$lang = Util::getUserLang();
|
||||||
foreach ($places as &$row) {
|
foreach ($places as &$row) {
|
||||||
$row['osm_id'] = (int) $row['osm_id'];
|
$row['osm_id'] = (int) $row['osm_id'];
|
||||||
$row['count'] = (int) $row['count'];
|
$row['count'] = $marked ? 0 : (int) $row['count']; // the count is incorrect
|
||||||
self::choosePlaceLang($row, $lang);
|
self::choosePlaceLang($row, $lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace OCA\Memories\Command;
|
||||||
use OCA\Memories\Service\Places;
|
use OCA\Memories\Service\Places;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class PlacesSetup extends Command
|
class PlacesSetup extends Command
|
||||||
|
@ -45,12 +46,14 @@ class PlacesSetup extends Command
|
||||||
$this
|
$this
|
||||||
->setName('memories:places-setup')
|
->setName('memories:places-setup')
|
||||||
->setDescription('Setup reverse geocoding')
|
->setDescription('Setup reverse geocoding')
|
||||||
|
->addOption('recalculate', 'r', InputOption::VALUE_NONE, 'Only recalculate places for existing files')
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
|
$recalculate = $input->getOption('recalculate');
|
||||||
|
|
||||||
$this->output->writeln('Attempting to set up reverse geocoding');
|
$this->output->writeln('Attempting to set up reverse geocoding');
|
||||||
|
|
||||||
|
@ -63,7 +66,29 @@ class PlacesSetup extends Command
|
||||||
$this->output->writeln('Database support was detected');
|
$this->output->writeln('Database support was detected');
|
||||||
|
|
||||||
// Check if database is already set up
|
// Check if database is already set up
|
||||||
if ($this->places->geomCount() > 0) {
|
if ($this->places->geomCount() > 0 && !$recalculate && !$this->warnDownloaded()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we only need to recalculate
|
||||||
|
if (!$recalculate) {
|
||||||
|
// Download the planet database
|
||||||
|
$datafile = $this->places->downloadPlanet();
|
||||||
|
|
||||||
|
// Import the planet database
|
||||||
|
$this->places->importPlanet($datafile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate all places
|
||||||
|
$this->places->recalculateAll();
|
||||||
|
|
||||||
|
$this->output->writeln('Done');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function warnDownloaded(): bool
|
||||||
|
{
|
||||||
$this->output->writeln('');
|
$this->output->writeln('');
|
||||||
$this->output->writeln('<error>Database is already set up</error>');
|
$this->output->writeln('<error>Database is already set up</error>');
|
||||||
$this->output->writeln('<error>This will drop and re-download the planet database</error>');
|
$this->output->writeln('<error>This will drop and re-download the planet database</error>');
|
||||||
|
@ -78,26 +103,14 @@ class PlacesSetup extends Command
|
||||||
if (false === $line) {
|
if (false === $line) {
|
||||||
$this->output->writeln('<error>You need an interactive terminal to run this command</error>');
|
$this->output->writeln('<error>You need an interactive terminal to run this command</error>');
|
||||||
|
|
||||||
return 1;
|
return false;
|
||||||
}
|
}
|
||||||
if ('y' !== trim($line)) {
|
if ('y' !== trim($line)) {
|
||||||
$this->output->writeln('Aborting');
|
$this->output->writeln('Aborting');
|
||||||
|
|
||||||
return 1;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the planet database
|
return true;
|
||||||
$datafile = $this->places->downloadPlanet();
|
|
||||||
|
|
||||||
// Import the planet database
|
|
||||||
$this->places->importPlanet($datafile);
|
|
||||||
|
|
||||||
// Recalculate all places
|
|
||||||
$this->places->recalculateAll();
|
|
||||||
|
|
||||||
$this->output->writeln('Done');
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,42 +46,25 @@ trait TimelineWritePlaces
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct WHERE clause depending on GIS type
|
// Get places
|
||||||
$where = null;
|
$rows = \OC::$server->get(\OCA\Memories\Service\Places::class)->queryPoint($lat, $lon);
|
||||||
if (1 === $gisType) {
|
|
||||||
$where = "ST_Contains(geometry, ST_GeomFromText('POINT({$lon} {$lat})'))";
|
|
||||||
} elseif (2 === $gisType) {
|
|
||||||
$where = "POINT('{$lon},{$lat}') <@ geometry";
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make query to memories_planet table
|
// Get last ID, i.e. the ID with highest admin_level but <= 8
|
||||||
$query = $this->connection->getQueryBuilder();
|
$markRow = array_pop(array_filter($rows, fn ($row) => $row['admin_level'] <= 8));
|
||||||
$query->select($query->createFunction('DISTINCT(osm_id)'))
|
|
||||||
->from('memories_planet_geometry')
|
|
||||||
->where($query->createFunction($where))
|
|
||||||
;
|
|
||||||
|
|
||||||
// Cancel out inner rings
|
|
||||||
$query->groupBy('poly_id', 'osm_id');
|
|
||||||
$query->having($query->createFunction('SUM(type_id) > 0'));
|
|
||||||
|
|
||||||
// memories_planet_geometry has no *PREFIX*
|
|
||||||
$sql = str_replace('*PREFIX*memories_planet_geometry', 'memories_planet_geometry', $query->getSQL());
|
|
||||||
|
|
||||||
// Run query
|
|
||||||
$rows = $this->connection->executeQuery($sql)->fetchAll();
|
|
||||||
|
|
||||||
// Insert records in transaction
|
// Insert records in transaction
|
||||||
$this->connection->beginTransaction();
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
$isMark = $markRow && $row['osm_id'] === $markRow['osm_id'];
|
||||||
|
|
||||||
|
// Insert the place
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$query->insert('memories_places')
|
$query->insert('memories_places')
|
||||||
->values([
|
->values([
|
||||||
'fileid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
|
'fileid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
|
||||||
'osm_id' => $query->createNamedParameter($row['osm_id'], IQueryBuilder::PARAM_INT),
|
'osm_id' => $query->createNamedParameter($row['osm_id'], IQueryBuilder::PARAM_INT),
|
||||||
|
'mark' => $query->createNamedParameter($isMark, IQueryBuilder::PARAM_BOOL),
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
$query->executeStatement();
|
$query->executeStatement();
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Memories\Migration;
|
||||||
|
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\DB\Types;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated migration step: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version502000Date20230530052850 extends SimpleMigrationStep
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param \Closure(): ISchemaWrapper $schemaClosure
|
||||||
|
*/
|
||||||
|
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Closure(): ISchemaWrapper $schemaClosure
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||||
|
{
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
$table = $schema->getTable('memories_places');
|
||||||
|
$table->addColumn('mark', Types::BOOLEAN, [
|
||||||
|
'notnull' => false,
|
||||||
|
'default' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->addIndex(['osm_id', 'mark'], 'memories_places_id_mk_idx');
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Closure(): ISchemaWrapper $schemaClosure
|
||||||
|
*/
|
||||||
|
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,6 +88,50 @@ class Places
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of osm IDs for a given point.
|
||||||
|
*/
|
||||||
|
public function queryPoint(float $lat, float $lon): array
|
||||||
|
{
|
||||||
|
// Get GIS type
|
||||||
|
$gisType = \OCA\Memories\Util::placesGISType();
|
||||||
|
|
||||||
|
// Construct WHERE clause depending on GIS type
|
||||||
|
$where = null;
|
||||||
|
if (1 === $gisType) {
|
||||||
|
$where = "ST_Contains(geometry, ST_GeomFromText('POINT({$lon} {$lat})'))";
|
||||||
|
} elseif (2 === $gisType) {
|
||||||
|
$where = "POINT('{$lon},{$lat}') <@ geometry";
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make query to memories_planet table
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select($query->createFunction('DISTINCT(osm_id)'))
|
||||||
|
->from('memories_planet_geometry')
|
||||||
|
->where($query->createFunction($where))
|
||||||
|
;
|
||||||
|
|
||||||
|
// Cancel out inner rings
|
||||||
|
$query->groupBy('poly_id', 'osm_id');
|
||||||
|
$query->having($query->createFunction('SUM(type_id) > 0'));
|
||||||
|
|
||||||
|
// memories_planet_geometry has no *PREFIX*
|
||||||
|
$sql = str_replace('*PREFIX*memories_planet_geometry', 'memories_planet_geometry', $query->getSQL());
|
||||||
|
|
||||||
|
// Use as subquery to get admin level
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('sub.osm_id', 'mp.admin_level')
|
||||||
|
->from($query->createFunction("({$sql})"), 'sub')
|
||||||
|
->innerJoin('sub', 'memories_planet', 'mp', $query->expr()->eq('sub.osm_id', 'mp.osm_id'))
|
||||||
|
->orderBy('mp.admin_level', 'ASC')
|
||||||
|
;
|
||||||
|
|
||||||
|
// Run query
|
||||||
|
return $query->executeQuery()->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download planet database file and return path to it.
|
* Download planet database file and return path to it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
:gridItems="gridItems"
|
:gridItems="gridItems"
|
||||||
@resize="resize"
|
@resize="resize"
|
||||||
>
|
>
|
||||||
|
<template #before>
|
||||||
|
<slot name="before" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-slot="{ item }">
|
<template v-slot="{ item }">
|
||||||
<div class="grid-item fill-block">
|
<div class="grid-item fill-block">
|
||||||
<Cluster :data="item" @click="click(item)" :link="link" />
|
<Cluster :data="item" @click="click(item)" :link="link" />
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="noParams" class="container no-user-select">
|
<div v-if="noParams" class="container no-user-select cluster-view">
|
||||||
<XLoadingIcon class="loading-icon centered" v-if="loading" />
|
<XLoadingIcon class="loading-icon centered" v-if="loading" />
|
||||||
|
|
||||||
<TopMatter />
|
<TopMatter />
|
||||||
|
|
||||||
<EmptyContent v-if="!items.length && !loading" />
|
<EmptyContent v-if="!items.length && !loading" />
|
||||||
|
|
||||||
<ClusterGrid :items="items" :minCols="minCols" />
|
<ClusterGrid :items="items" :minCols="minCols" ref="denali">
|
||||||
|
<template #before>
|
||||||
|
<DynamicTopMatter ref="dtm" />
|
||||||
|
</template>
|
||||||
|
</ClusterGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Timeline v-else />
|
<Timeline v-else />
|
||||||
|
@ -22,6 +26,7 @@ import TopMatter from './top-matter/TopMatter.vue';
|
||||||
import ClusterGrid from './ClusterGrid.vue';
|
import ClusterGrid from './ClusterGrid.vue';
|
||||||
import Timeline from './Timeline.vue';
|
import Timeline from './Timeline.vue';
|
||||||
import EmptyContent from './top-matter/EmptyContent.vue';
|
import EmptyContent from './top-matter/EmptyContent.vue';
|
||||||
|
import DynamicTopMatter from './top-matter/DynamicTopMatter.vue';
|
||||||
|
|
||||||
import * as dav from '../services/DavRequests';
|
import * as dav from '../services/DavRequests';
|
||||||
|
|
||||||
|
@ -35,6 +40,7 @@ export default defineComponent({
|
||||||
ClusterGrid,
|
ClusterGrid,
|
||||||
Timeline,
|
Timeline,
|
||||||
EmptyContent,
|
EmptyContent,
|
||||||
|
DynamicTopMatter,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [UserConfig],
|
mixins: [UserConfig],
|
||||||
|
@ -79,6 +85,10 @@ export default defineComponent({
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.loading++;
|
this.loading++;
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
// @ts-ignore
|
||||||
|
await this.$refs.dtm?.refresh?.();
|
||||||
|
|
||||||
if (route === 'albums') {
|
if (route === 'albums') {
|
||||||
this.items = await dav.getAlbums(3, this.config.album_list_sort);
|
this.items = await dav.getAlbums(3, this.config.album_list_sort);
|
||||||
} else if (route === 'tags') {
|
} else if (route === 'tags') {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { defineComponent } from 'vue';
|
||||||
import UserMixin from '../../mixins/UserConfig';
|
import UserMixin from '../../mixins/UserConfig';
|
||||||
|
|
||||||
import FolderDynamicTopMatter from './FolderDynamicTopMatter.vue';
|
import FolderDynamicTopMatter from './FolderDynamicTopMatter.vue';
|
||||||
|
import PlacesDynamicTopMatterVue from './PlacesDynamicTopMatter.vue';
|
||||||
import OnThisDay from './OnThisDay.vue';
|
import OnThisDay from './OnThisDay.vue';
|
||||||
|
|
||||||
import * as PublicShareHeader from './PublicShareHeader';
|
import * as PublicShareHeader from './PublicShareHeader';
|
||||||
|
@ -25,6 +26,8 @@ export default defineComponent({
|
||||||
currentmatter(): any {
|
currentmatter(): any {
|
||||||
if (this.routeIsFolders) {
|
if (this.routeIsFolders) {
|
||||||
return FolderDynamicTopMatter;
|
return FolderDynamicTopMatter;
|
||||||
|
} else if (this.routeIsPlaces) {
|
||||||
|
return PlacesDynamicTopMatterVue;
|
||||||
} else if (this.routeIsBase && this.config.enable_top_memories) {
|
} else if (this.routeIsBase && this.config.enable_top_memories) {
|
||||||
return OnThisDay;
|
return OnThisDay;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div class="places-dtm">
|
||||||
|
<NcButton class="place" :key="place.cluster_id" v-for="place of places" :to="route(place)">
|
||||||
|
{{ place.name }}
|
||||||
|
</NcButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import axios from '@nextcloud/axios';
|
||||||
|
import NcButton from '@nextcloud/vue/dist/Components/NcButton';
|
||||||
|
|
||||||
|
import { API } from '../../services/API';
|
||||||
|
|
||||||
|
import type { ICluster } from '../../types';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PlacesDynamicTopMatter',
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
places: [] as ICluster[],
|
||||||
|
}),
|
||||||
|
|
||||||
|
components: {
|
||||||
|
NcButton,
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async refresh(): Promise<boolean> {
|
||||||
|
// Clear folders
|
||||||
|
this.places = [];
|
||||||
|
|
||||||
|
// Get ID of place from URL
|
||||||
|
const placeId = Number(this.$route.params.name?.split('-')[0]) || -1;
|
||||||
|
const url = API.Q(API.PLACE_LIST(), { inside: placeId });
|
||||||
|
|
||||||
|
// Make API call to get subfolders
|
||||||
|
try {
|
||||||
|
this.places = (await axios.get<ICluster[]>(url)).data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.places.length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
route(place: ICluster) {
|
||||||
|
return {
|
||||||
|
name: 'places',
|
||||||
|
params: {
|
||||||
|
name: place.cluster_id + '-' + place.name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.places-dtm {
|
||||||
|
margin: 0 0.3em;
|
||||||
|
|
||||||
|
button.place {
|
||||||
|
font-size: 0.85em;
|
||||||
|
min-height: unset;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 3px 2px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
margin-right: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -97,6 +97,11 @@ export default defineComponent({
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide shadow if inside cluster view
|
||||||
|
.cluster-view & {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
padding-left: 10px; // extra space visual
|
padding-left: 10px; // extra space visual
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue