Merge branch 'master' into stable24
commit
37501bcbdf
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -2,6 +2,17 @@
|
|||
|
||||
This file is manually updated. Please file an issue if something is missing.
|
||||
|
||||
## v4.6.0, v3.6.0 (2022-11-06)
|
||||
|
||||
- **Brand new photo viewer** with improved touch interface and UX
|
||||
- Improvements from v4.5.4 below
|
||||
- Known regressions: Photo Editor and Slideshow are not implemented yet
|
||||
|
||||
## v4.5.4, v3.5.4 (skipped)
|
||||
|
||||
- New layout for Albums view (date ascending, grouped by month)
|
||||
- Re-enable viewer editing and deletion
|
||||
|
||||
## v4.5.2, v3.5.2 (2022-10-30)
|
||||
|
||||
- Improved scroller performance
|
||||
|
|
|
@ -133,6 +133,26 @@ class ApiBase extends Controller
|
|||
return $folder;
|
||||
}
|
||||
|
||||
protected function isRecursive()
|
||||
{
|
||||
return null === $this->request->getParam('folder');
|
||||
}
|
||||
|
||||
protected function isArchive()
|
||||
{
|
||||
return null !== $this->request->getParam('archive');
|
||||
}
|
||||
|
||||
protected function isMonthView()
|
||||
{
|
||||
return null !== $this->request->getParam('monthView');
|
||||
}
|
||||
|
||||
protected function isReverse()
|
||||
{
|
||||
return null !== $this->request->getParam('reverse');
|
||||
}
|
||||
|
||||
protected function getShareToken()
|
||||
{
|
||||
return $this->request->getParam('folder_share');
|
||||
|
|
|
@ -49,25 +49,31 @@ class DaysController extends ApiBase
|
|||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Params
|
||||
$recursive = null === $this->request->getParam('folder');
|
||||
$archive = null !== $this->request->getParam('archive');
|
||||
|
||||
// Run actual query
|
||||
try {
|
||||
$list = $this->timelineQuery->getDays(
|
||||
$folder,
|
||||
$uid,
|
||||
$recursive,
|
||||
$archive,
|
||||
$this->isRecursive(),
|
||||
$this->isArchive(),
|
||||
$this->getTransformations(true),
|
||||
);
|
||||
|
||||
// Preload some day responses
|
||||
$this->preloadDays($list, $uid, $folder, $recursive, $archive);
|
||||
if ($this->isMonthView()) {
|
||||
// Group days together into months
|
||||
$list = $this->timelineQuery->daysToMonths($list);
|
||||
} else {
|
||||
// Preload some day responses
|
||||
$this->preloadDays($list, $uid, $folder);
|
||||
}
|
||||
|
||||
// Reverse response if requested. Folders still stay at top.
|
||||
if ($this->isReverse()) {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
|
||||
// Add subfolder info if querying non-recursively
|
||||
if (!$recursive) {
|
||||
if (!$this->isRecursive()) {
|
||||
array_unshift($list, $this->getSubfoldersEntry($folder));
|
||||
}
|
||||
|
||||
|
@ -88,18 +94,18 @@ class DaysController extends ApiBase
|
|||
$uid = $this->getUid();
|
||||
|
||||
// Check for wildcard
|
||||
$day_ids = [];
|
||||
$dayIds = [];
|
||||
if ('*' === $id) {
|
||||
$day_ids = null;
|
||||
$dayIds = null;
|
||||
} else {
|
||||
// Split at commas and convert all parts to int
|
||||
$day_ids = array_map(function ($part) {
|
||||
$dayIds = array_map(function ($part) {
|
||||
return (int) $part;
|
||||
}, explode(',', $id));
|
||||
}
|
||||
|
||||
// Check if $day_ids is empty
|
||||
if (null !== $day_ids && 0 === \count($day_ids)) {
|
||||
// Check if $dayIds is empty
|
||||
if (null !== $dayIds && 0 === \count($dayIds)) {
|
||||
return new JSONResponse([], Http::STATUS_OK);
|
||||
}
|
||||
|
||||
|
@ -112,21 +118,34 @@ class DaysController extends ApiBase
|
|||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Params
|
||||
$recursive = null === $this->request->getParam('folder');
|
||||
$archive = null !== $this->request->getParam('archive');
|
||||
// Convert to actual dayIds if month view
|
||||
if ($this->isMonthView()) {
|
||||
$dayIds = $this->timelineQuery->monthIdToDayIds($dayIds[0]);
|
||||
}
|
||||
|
||||
// Run actual query
|
||||
try {
|
||||
$list = $this->timelineQuery->getDay(
|
||||
$folder,
|
||||
$uid,
|
||||
$day_ids,
|
||||
$recursive,
|
||||
$archive,
|
||||
$dayIds,
|
||||
$this->isRecursive(),
|
||||
$this->isArchive(),
|
||||
$this->getTransformations(false),
|
||||
);
|
||||
|
||||
// Force month id for dayId for month view
|
||||
if ($this->isMonthView()) {
|
||||
foreach ($list as &$photo) {
|
||||
$photo['dayid'] = (int) $dayIds[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse response if requested.
|
||||
if ($this->isReverse()) {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
|
||||
return new JSONResponse($list, Http::STATUS_OK);
|
||||
} catch (\Exception $e) {
|
||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
|
@ -252,13 +271,11 @@ class DaysController extends ApiBase
|
|||
/**
|
||||
* Preload a few "day" at the start of "days" response.
|
||||
*
|
||||
* @param array $days the days array
|
||||
* @param string $uid User ID or blank for public shares
|
||||
* @param null|Folder $folder the folder to search in
|
||||
* @param bool $recursive search in subfolders
|
||||
* @param bool $archive search in archive folder only
|
||||
* @param array $days the days array
|
||||
* @param string $uid User ID or blank for public shares
|
||||
* @param null|Folder $folder the folder to search in
|
||||
*/
|
||||
private function preloadDays(array &$days, string $uid, &$folder, bool $recursive, bool $archive)
|
||||
private function preloadDays(array &$days, string $uid, &$folder)
|
||||
{
|
||||
$transforms = $this->getTransformations(false);
|
||||
$preloaded = 0;
|
||||
|
@ -283,8 +300,8 @@ class DaysController extends ApiBase
|
|||
$folder,
|
||||
$uid,
|
||||
$preloadDayIds,
|
||||
$recursive,
|
||||
$archive,
|
||||
$this->isRecursive(),
|
||||
$this->isArchive(),
|
||||
$transforms,
|
||||
);
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace OCA\Memories\Controller;
|
|||
|
||||
use OCA\Files\Event\LoadSidebar;
|
||||
use OCA\Memories\AppInfo\Application;
|
||||
use OCA\Viewer\Event\LoadViewer;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
|
@ -62,7 +61,6 @@ class PageController extends Controller
|
|||
// Scripts
|
||||
Util::addScript($this->appName, 'memories-main');
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
|
||||
// Configuration
|
||||
$uid = $user->getUid();
|
||||
|
@ -114,7 +112,6 @@ class PageController extends Controller
|
|||
// Scripts
|
||||
Util::addScript($this->appName, 'memories-main');
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
|
||||
// App version
|
||||
$this->initialState->provideInitialState('version', $this->appManager->getAppInfo('memories')['version']);
|
||||
|
|
|
@ -78,6 +78,44 @@ trait TimelineQueryAlbums
|
|||
return $albums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert days response to months response.
|
||||
* The dayId is used to group the days into months.
|
||||
*/
|
||||
public function daysToMonths(array &$days)
|
||||
{
|
||||
$months = [];
|
||||
foreach ($days as &$day) {
|
||||
$dayId = $day['dayid'];
|
||||
$time = $dayId * 86400;
|
||||
$monthid = strtotime(date('Ym', $time).'01') / 86400;
|
||||
|
||||
if (empty($months) || $months[\count($months) - 1]['dayid'] !== $monthid) {
|
||||
$months[] = [
|
||||
'dayid' => $monthid,
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$months[\count($months) - 1]['count'] += $day['count'];
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
/** Convert list of month IDs to list of dayIds */
|
||||
public function monthIdToDayIds(int $monthId)
|
||||
{
|
||||
$dayIds = [];
|
||||
$firstDay = (int) $monthId;
|
||||
$lastDay = strtotime(date('Ymt', $firstDay * 86400)) / 86400;
|
||||
for ($i = $firstDay; $i <= $lastDay; ++$i) {
|
||||
$dayIds[] = (string) $i;
|
||||
}
|
||||
|
||||
return $dayIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get album if allowed. Also check if album is shared with user.
|
||||
*
|
||||
|
|
|
@ -78,7 +78,7 @@ trait TimelineQueryDays
|
|||
*
|
||||
* @param null|Folder $folder The folder to get the day from
|
||||
* @param string $uid The user id
|
||||
* @param int[] $dayid The day id
|
||||
* @param int[] $day_ids The day ids to fetch
|
||||
* @param bool $recursive If the query should be recursive
|
||||
* @param bool $archive If the query should include only the archive folder
|
||||
* @param array $queryTransforms The query transformations to apply
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
|
@ -32,11 +31,11 @@ use OCP\Migration\SimpleMigrationStep;
|
|||
class Version000000Date20220812163631 extends SimpleMigrationStep
|
||||
{
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options)
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options)
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
|
@ -43,9 +42,9 @@ class Version200000Date20220924015634 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -59,9 +58,9 @@ class Version200000Date20220924015634 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -85,9 +84,9 @@ class Version200000Date20220924015634 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
|
@ -35,16 +34,16 @@ use OCP\Migration\SimpleMigrationStep;
|
|||
class Version400000Date20221015121115 extends SimpleMigrationStep
|
||||
{
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -68,9 +67,9 @@ class Version400000Date20221015121115 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
@ -34,16 +33,16 @@ use OCP\Migration\SimpleMigrationStep;
|
|||
class Version400307Date20221025002524 extends SimpleMigrationStep
|
||||
{
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -59,9 +58,9 @@ class Version400307Date20221025002524 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
@ -34,16 +33,16 @@ use OCP\Migration\SimpleMigrationStep;
|
|||
class Version400308Date20221026151748 extends SimpleMigrationStep
|
||||
{
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -57,9 +56,9 @@ class Version400308Date20221026151748 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
|
@ -43,16 +42,16 @@ class Version400503Date20221101033144 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
|
||||
{
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
@ -76,9 +75,9 @@ class Version400503Date20221101033144 extends SimpleMigrationStep
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
*/
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
|
||||
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
|
||||
{
|
||||
// Update oc_memories to set objectid equal to fileid for all rows
|
||||
$this->dbc->executeQuery('UPDATE *PREFIX*memories SET objectid = CAST(fileid AS CHAR(64))');
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
"justified-layout": "^4.1.0",
|
||||
"moment": "^2.29.4",
|
||||
"path-posix": "^1.0.0",
|
||||
"photoswipe": "^5.3.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"video.js": "^7.20.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-class-component": "^7.2.6",
|
||||
"vue-material-design-icons": "^5.1.2",
|
||||
|
@ -2334,6 +2336,52 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/http-streaming": {
|
||||
"version": "2.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz",
|
||||
"integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "3.0.5",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"video.js": "^6 || ^7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/vhs-utils": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@videojs/xhr": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz",
|
||||
"integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"global": "~4.4.0",
|
||||
"is-function": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.13.tgz",
|
||||
|
@ -2589,6 +2637,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz",
|
||||
"integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -2652,6 +2708,17 @@
|
|||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/aes-decrypter": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
|
||||
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
|
@ -3990,6 +4057,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/domain-browser": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz",
|
||||
|
@ -4818,6 +4890,15 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"dependencies": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
|
@ -5217,6 +5298,11 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/individual": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
|
||||
"integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g=="
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
|
@ -5395,6 +5481,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="
|
||||
},
|
||||
"node_modules/is-generator-function": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||
|
@ -5893,6 +5984,11 @@
|
|||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"node_modules/keycode": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
|
||||
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
@ -6018,6 +6114,16 @@
|
|||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/m3u8-parser": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz",
|
||||
"integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
|
@ -6202,6 +6308,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-document": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
|
||||
"dependencies": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
|
@ -6245,6 +6359,20 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/mpd-parser": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
|
||||
"integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@xmldom/xmldom": "^0.7.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"mpd-to-m3u8-json": "bin/parse.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -6265,6 +6393,22 @@
|
|||
"multicast-dns": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mux.js": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
|
||||
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"muxjs-transmux": "bin/transmux.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
|
@ -6673,6 +6817,14 @@
|
|||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/photoswipe": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.3.tgz",
|
||||
"integrity": "sha512-BUuulwZwkYFKADSe5xf0dd+wf6dws34ZvqP8R3oYHepRauOXoQHvw600sw1HlWd8K0S3LRCS4jxyR5fTuI383Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -6690,6 +6842,17 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pkcs7": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
|
||||
"integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5"
|
||||
},
|
||||
"bin": {
|
||||
"pkcs7": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||
|
@ -6871,8 +7034,6 @@
|
|||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
|
@ -7349,6 +7510,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/rust-result": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
|
||||
"integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==",
|
||||
"dependencies": {
|
||||
"individual": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -7369,6 +7538,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safe-json-parse": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
|
||||
"integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==",
|
||||
"dependencies": {
|
||||
"rust-result": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
||||
|
@ -8615,6 +8792,11 @@
|
|||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-toolkit": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
|
||||
},
|
||||
"node_modules/url/node_modules/punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
|
@ -8680,6 +8862,39 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/video.js": {
|
||||
"version": "7.20.3",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz",
|
||||
"integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "2.14.3",
|
||||
"@videojs/vhs-utils": "^3.0.4",
|
||||
"@videojs/xhr": "2.6.0",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"keycode": "^2.2.0",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"videojs-font": "3.2.0",
|
||||
"videojs-vtt.js": "^0.15.4"
|
||||
}
|
||||
},
|
||||
"node_modules/videojs-font": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
|
||||
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
|
||||
},
|
||||
"node_modules/videojs-vtt.js": {
|
||||
"version": "0.15.4",
|
||||
"resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz",
|
||||
"integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==",
|
||||
"dependencies": {
|
||||
"global": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
|
@ -11414,6 +11629,41 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@videojs/http-streaming": {
|
||||
"version": "2.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz",
|
||||
"integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "3.0.5",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"video.js": "^6 || ^7"
|
||||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"@videojs/xhr": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz",
|
||||
"integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"global": "~4.4.0",
|
||||
"is-function": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.13.tgz",
|
||||
|
@ -11649,6 +11899,11 @@
|
|||
"peer": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz",
|
||||
"integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA=="
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -11698,6 +11953,17 @@
|
|||
"peer": true,
|
||||
"requires": {}
|
||||
},
|
||||
"aes-decrypter": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
|
||||
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
|
@ -12765,6 +13031,11 @@
|
|||
"@leichtgewicht/ip-codec": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"domain-browser": {
|
||||
"version": "4.22.0",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz",
|
||||
|
@ -13423,6 +13694,15 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"requires": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
|
@ -13736,6 +14016,11 @@
|
|||
"resolve-cwd": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"individual": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
|
||||
"integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g=="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
|
@ -13860,6 +14145,11 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"is-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="
|
||||
},
|
||||
"is-generator-function": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||
|
@ -14216,6 +14506,11 @@
|
|||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"keycode": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
|
||||
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
@ -14321,6 +14616,16 @@
|
|||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"m3u8-parser": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz",
|
||||
"integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
|
||||
|
@ -14471,6 +14776,14 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"min-document": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
|
||||
"requires": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
|
@ -14505,6 +14818,17 @@
|
|||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"mpd-parser": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz",
|
||||
"integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@xmldom/xmldom": "^0.7.2",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -14522,6 +14846,15 @@
|
|||
"thunky": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"mux.js": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
|
||||
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
|
@ -14840,6 +15173,11 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"photoswipe": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.3.tgz",
|
||||
"integrity": "sha512-BUuulwZwkYFKADSe5xf0dd+wf6dws34ZvqP8R3oYHepRauOXoQHvw600sw1HlWd8K0S3LRCS4jxyR5fTuI383Q=="
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -14851,6 +15189,14 @@
|
|||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true
|
||||
},
|
||||
"pkcs7": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
|
||||
"integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5"
|
||||
}
|
||||
},
|
||||
"pkg-dir": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||
|
@ -14961,9 +15307,7 @@
|
|||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
|
@ -15347,12 +15691,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"rust-result": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
|
||||
"integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==",
|
||||
"requires": {
|
||||
"individual": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"safe-json-parse": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
|
||||
"integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==",
|
||||
"requires": {
|
||||
"rust-result": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-regex-test": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
|
||||
|
@ -16310,6 +16670,11 @@
|
|||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"url-toolkit": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||
|
@ -16356,6 +16721,39 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"video.js": {
|
||||
"version": "7.20.3",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz",
|
||||
"integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "2.14.3",
|
||||
"@videojs/vhs-utils": "^3.0.4",
|
||||
"@videojs/xhr": "2.6.0",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"keycode": "^2.2.0",
|
||||
"m3u8-parser": "4.7.1",
|
||||
"mpd-parser": "0.21.1",
|
||||
"mux.js": "6.0.1",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"videojs-font": "3.2.0",
|
||||
"videojs-vtt.js": "^0.15.4"
|
||||
}
|
||||
},
|
||||
"videojs-font": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
|
||||
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
|
||||
},
|
||||
"videojs-vtt.js": {
|
||||
"version": "0.15.4",
|
||||
"resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz",
|
||||
"integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==",
|
||||
"requires": {
|
||||
"global": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
|
|
|
@ -37,7 +37,9 @@
|
|||
"justified-layout": "^4.1.0",
|
||||
"moment": "^2.29.4",
|
||||
"path-posix": "^1.0.0",
|
||||
"photoswipe": "^5.3.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"video.js": "^7.20.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-class-component": "^7.2.6",
|
||||
"vue-material-design-icons": "^5.1.2",
|
||||
|
|
12
src/App.vue
12
src/App.vue
|
@ -271,11 +271,14 @@ body {
|
|||
}
|
||||
|
||||
// Top bar is above everything else on mobile
|
||||
#content-vue.has-top-bar {
|
||||
body.has-top-bar header {
|
||||
@media (max-width: 1024px) {
|
||||
z-index: 3000;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
}
|
||||
body.has-viewer header {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
// Patch viewer to remove the title and
|
||||
// make the image fill the entire screen
|
||||
|
@ -286,6 +289,11 @@ body {
|
|||
.modal-wrapper .modal-container {
|
||||
top: 0 !important;
|
||||
bottom: 0 !important;
|
||||
|
||||
.viewer__image-editor {
|
||||
top: 0 !important;
|
||||
bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
// Get cursor px position
|
||||
const { top1, top2, y1, y2 } = this.getCoords(scroll, "y");
|
||||
const topfrac = (scroll - y1) / (y2 - y1);
|
||||
const rtop = top1 + (top2 - top1) * topfrac;
|
||||
const rtop = top1 + (top2 - top1) * (topfrac || 0);
|
||||
|
||||
// Always move static cursor to right position
|
||||
this.cursorY = rtop;
|
||||
|
@ -200,7 +200,6 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
// Ticks
|
||||
let prevYear = 9999;
|
||||
let prevMonth = 0;
|
||||
const thisYear = new Date().getFullYear();
|
||||
|
||||
// Get a new tick
|
||||
const getTick = (
|
||||
|
@ -235,8 +234,7 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
const dtYear = dateTaken.getUTCFullYear();
|
||||
const dtMonth = dateTaken.getUTCMonth();
|
||||
const isMonth = dtMonth !== prevMonth || dtYear !== prevYear;
|
||||
const text =
|
||||
dtYear === prevYear || dtYear === thisYear ? undefined : dtYear;
|
||||
const text = dtYear === prevYear ? undefined : dtYear;
|
||||
this.ticks.push(getTick(row.dayId, isMonth, text));
|
||||
|
||||
prevMonth = dtMonth;
|
||||
|
@ -459,7 +457,7 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
|
||||
const { top1, top2, y1, y2 } = this.getCoords(y, "topF");
|
||||
const yfrac = (y - top1) / (top2 - top1);
|
||||
const ry = y1 + (y2 - y1) * yfrac;
|
||||
const ry = y1 + (y2 - y1) * (yfrac || 0);
|
||||
this.recycler.scrollToPosition(ry);
|
||||
|
||||
this.handleScroll();
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
<div class="text">
|
||||
{{
|
||||
n("memories", "{n} selected", "{n} selected", selection.size, {
|
||||
n: selection.size,
|
||||
n("memories", "{n} selected", "{n} selected", size, {
|
||||
n: size,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@ import { showError } from "@nextcloud/dialogs";
|
|||
import { generateUrl } from "@nextcloud/router";
|
||||
import { NcActions, NcActionButton } from "@nextcloud/vue";
|
||||
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||
import { IHeadRow, IPhoto, ISelectionAction } from "../types";
|
||||
import { IDay, IHeadRow, IPhoto, ISelectionAction } from "../types";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
|
@ -92,6 +92,7 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
@Prop() public heads: { [dayid: number]: IHeadRow };
|
||||
|
||||
private show = false;
|
||||
private size = 0;
|
||||
private readonly selection!: Selection;
|
||||
private readonly defaultActions: ISelectionAction[];
|
||||
|
||||
|
@ -182,17 +183,17 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
@Watch("show")
|
||||
onShowChange() {
|
||||
const elem = document.getElementById("content-vue");
|
||||
const klass = "has-top-bar";
|
||||
if (this.show) {
|
||||
elem.classList.add(klass);
|
||||
document.body.classList.add(klass);
|
||||
} else {
|
||||
elem.classList.remove(klass);
|
||||
document.body.classList.remove(klass);
|
||||
}
|
||||
}
|
||||
|
||||
private selectionChanged() {
|
||||
this.show = this.selection.size > 0;
|
||||
this.size = this.selection.size;
|
||||
}
|
||||
|
||||
/** Is this fileid (or anything if not specified) selected */
|
||||
|
@ -203,6 +204,39 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
return this.selection.has(fileid);
|
||||
}
|
||||
|
||||
/** Restore selections from new day object */
|
||||
public restoreDay(day: IDay) {
|
||||
if (!this.has()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FileID => Photo for new day
|
||||
const dayMap = new Map<number, IPhoto>();
|
||||
day.detail.forEach((photo) => {
|
||||
dayMap.set(photo.fileid, photo);
|
||||
});
|
||||
|
||||
this.selection.forEach((photo, fileid) => {
|
||||
// Process this day only
|
||||
if (photo.dayid !== day.dayid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all selections that are not in the new day
|
||||
if (!dayMap.has(fileid)) {
|
||||
this.selection.delete(fileid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the photo object
|
||||
const newPhoto = dayMap.get(fileid);
|
||||
this.selection.set(fileid, newPhoto);
|
||||
newPhoto.flag |= this.c.FLAG_SELECTED;
|
||||
});
|
||||
|
||||
this.selectionChanged();
|
||||
}
|
||||
|
||||
/** Click on an action */
|
||||
private async click(action: ISelectionAction) {
|
||||
try {
|
||||
|
@ -335,18 +369,6 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
Array.from(selection.values()),
|
||||
val
|
||||
)) {
|
||||
favIds.forEach((id) => {
|
||||
const photo = selection.get(id);
|
||||
if (!photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val) {
|
||||
photo.flag |= this.c.FLAG_IS_FAVORITE;
|
||||
} else {
|
||||
photo.flag &= ~this.c.FLAG_IS_FAVORITE;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.clearSelection();
|
||||
}
|
||||
|
@ -391,17 +413,7 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
*/
|
||||
private async viewInFolder(selection: Selection) {
|
||||
if (selection.size !== 1) return;
|
||||
|
||||
const photo: IPhoto = selection.values().next().value;
|
||||
const f = await dav.getFiles([photo]);
|
||||
if (f.length === 0) return;
|
||||
|
||||
const file = f[0];
|
||||
const dirPath = file.filename.split("/").slice(0, -1).join("/");
|
||||
const url = generateUrl(
|
||||
`/apps/files/?dir=${dirPath}&scrollto=${file.fileid}&openfile=${file.fileid}`
|
||||
);
|
||||
window.open(url, "_blank");
|
||||
dav.viewInFolder(selection.values().next().value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
|
||||
<OnThisDay
|
||||
v-if="$route.name === 'timeline'"
|
||||
:viewerManager="viewerManager"
|
||||
:key="config_timelinePath"
|
||||
:viewer="$refs.viewer"
|
||||
@load="scrollerManager.adjust()"
|
||||
>
|
||||
</OnThisDay>
|
||||
|
@ -121,35 +121,46 @@
|
|||
@delete="deleteFromViewWithAnimation"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
|
||||
<Viewer
|
||||
ref="viewer"
|
||||
@deleted="deleteFromViewWithAnimation"
|
||||
@fetchDay="fetchDay"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Watch } from "vue-property-decorator";
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
|
||||
import axios from "@nextcloud/axios";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { subscribe, unsubscribe } from "@nextcloud/event-bus";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { NcEmptyContent } from "@nextcloud/vue";
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
import { Component, Mixins, Watch } from "vue-property-decorator";
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
import * as dav from "../services/DavRequests";
|
||||
|
||||
import { getLayout } from "../services/Layout";
|
||||
import * as utils from "../services/Utils";
|
||||
import { ViewerManager } from "../services/Viewer";
|
||||
import { IDay, IFolder, IHeadRow, IPhoto, IRow, IRowType } from "../types";
|
||||
import Folder from "./frame/Folder.vue";
|
||||
import Photo from "./frame/Photo.vue";
|
||||
import Tag from "./frame/Tag.vue";
|
||||
import ScrollerManager from "./ScrollerManager.vue";
|
||||
import SelectionManager from "./SelectionManager.vue";
|
||||
import Viewer from "./Viewer.vue";
|
||||
import OnThisDay from "./top-matter/OnThisDay.vue";
|
||||
import TopMatter from "./top-matter/TopMatter.vue";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
|
||||
const SCROLL_LOAD_DELAY = 100; // Delay in loading data when scrolling
|
||||
const DESKTOP_ROW_HEIGHT = 200; // Height of row on desktop
|
||||
const MOBILE_ROW_HEIGHT = 120; // Approx row height on mobile
|
||||
|
@ -163,6 +174,7 @@ const MOBILE_ROW_HEIGHT = 120; // Approx row height on mobile
|
|||
OnThisDay,
|
||||
SelectionManager,
|
||||
ScrollerManager,
|
||||
Viewer,
|
||||
NcEmptyContent,
|
||||
|
||||
CheckCircle,
|
||||
|
@ -212,13 +224,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
/** Scroller manager component */
|
||||
private scrollerManager!: ScrollerManager & any;
|
||||
|
||||
/** Nextcloud viewer proxy */
|
||||
private viewerManager = new ViewerManager(
|
||||
this.deleteFromViewWithAnimation.bind(this),
|
||||
this.updateLoading.bind(this),
|
||||
this.$route
|
||||
);
|
||||
|
||||
mounted() {
|
||||
this.selectionManager = this.$refs.selectionManager;
|
||||
this.scrollerManager = this.$refs.scrollerManager;
|
||||
|
@ -256,6 +261,10 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
return window.innerWidth <= 600;
|
||||
}
|
||||
|
||||
isMonthView() {
|
||||
return this.$route.name === "albums";
|
||||
}
|
||||
|
||||
allowBreakout() {
|
||||
return this.isMobileLayout() && !this.config_squareThumbs;
|
||||
}
|
||||
|
@ -534,6 +543,12 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
query.set("folder_share", this.$route.params.token);
|
||||
}
|
||||
|
||||
// Month view
|
||||
if (this.isMonthView()) {
|
||||
query.set("monthView", "1");
|
||||
query.set("reverse", "1");
|
||||
}
|
||||
|
||||
// Create query string and append to URL
|
||||
const queryStr = query.toString();
|
||||
if (queryStr) {
|
||||
|
@ -582,7 +597,12 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
// The reason this function is separate from processDays is
|
||||
// because this call is terribly slow even on desktop
|
||||
const dateTaken = utils.dayIdToDate(head.dayId);
|
||||
const name = utils.getLongDateStr(dateTaken, true);
|
||||
let name: string;
|
||||
if (this.isMonthView()) {
|
||||
name = utils.getMonthDateStr(dateTaken);
|
||||
} else {
|
||||
name = utils.getLongDateStr(dateTaken, true);
|
||||
}
|
||||
|
||||
// Cache and return
|
||||
head.name = name;
|
||||
|
@ -782,6 +802,13 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
// Aggregate fetch requests
|
||||
this.fetchDayQueue.push(dayId);
|
||||
|
||||
// Only single queries allowed for month vie
|
||||
if (this.isMonthView()) {
|
||||
return this.fetchDayExpire();
|
||||
}
|
||||
|
||||
// Defer for aggregation
|
||||
if (!this.fetchDayTimer) {
|
||||
this.fetchDayTimer = window.setTimeout(() => {
|
||||
this.fetchDayTimer = null;
|
||||
|
@ -1029,6 +1056,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
row.photos.push(photo);
|
||||
}
|
||||
|
||||
// Restore selection day
|
||||
this.selectionManager.restoreDay(day);
|
||||
|
||||
// Rows that were removed
|
||||
const removedRows: IRow[] = [];
|
||||
let headRemoved = false;
|
||||
|
@ -1116,7 +1146,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
// selection mode
|
||||
this.selectionManager.selectPhoto(photo);
|
||||
} else {
|
||||
this.viewerManager.open(photo);
|
||||
(<any>this.$refs.viewer).open(photo, this.list);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,657 @@
|
|||
<template>
|
||||
<div
|
||||
class="memories_viewer outer"
|
||||
v-if="show"
|
||||
:class="{ fullyOpened }"
|
||||
:style="{ width: outerWidth }"
|
||||
>
|
||||
<div class="inner" ref="inner">
|
||||
<div class="top-bar" v-if="photoswipe" :class="{ opened }">
|
||||
<NcActions :inline="3" container=".memories_viewer .pswp">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Delete')"
|
||||
@click="deleteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Delete") }}
|
||||
<template #icon> <DeleteIcon :size="24" /> </template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Favorite')"
|
||||
@click="favoriteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Favorite") }}
|
||||
<template #icon>
|
||||
<StarIcon v-if="isFavorite()" :size="24" />
|
||||
<StarOutlineIcon v-else :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Sidebar')"
|
||||
@click="toggleSidebar"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Sidebar") }}
|
||||
<template #icon>
|
||||
<InfoIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Download')"
|
||||
@click="downloadCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Download") }}
|
||||
<template #icon>
|
||||
<DownloadIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'View in folder')"
|
||||
@click="viewInFolder"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "View in folder") }}
|
||||
<template #icon>
|
||||
<OpenInNewIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
||||
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import { IDay, IPhoto, IRow, IRowType } from "../types";
|
||||
|
||||
import { NcActions, NcActionButton } from "@nextcloud/vue";
|
||||
import { subscribe, unsubscribe } from "@nextcloud/event-bus";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
import { getPreviewUrl } from "../services/FileUtils";
|
||||
import { getAlbumFileInfos } from "../services/DavRequests";
|
||||
|
||||
import PhotoSwipe, { PhotoSwipeOptions } from "photoswipe";
|
||||
import "photoswipe/style.css";
|
||||
|
||||
import videojs from "video.js";
|
||||
import "video.js/dist/video-js.css";
|
||||
|
||||
import DeleteIcon from "vue-material-design-icons/Delete.vue";
|
||||
import StarIcon from "vue-material-design-icons/Star.vue";
|
||||
import StarOutlineIcon from "vue-material-design-icons/StarOutline.vue";
|
||||
import DownloadIcon from "vue-material-design-icons/Download.vue";
|
||||
import InfoIcon from "vue-material-design-icons/InformationOutline.vue";
|
||||
import OpenInNewIcon from "vue-material-design-icons/OpenInNew.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
DeleteIcon,
|
||||
StarIcon,
|
||||
StarOutlineIcon,
|
||||
DownloadIcon,
|
||||
InfoIcon,
|
||||
OpenInNewIcon,
|
||||
},
|
||||
})
|
||||
export default class Viewer extends Mixins(GlobalMixin) {
|
||||
@Emit("deleted") deleted(photos: IPhoto[]) {}
|
||||
@Emit("fetchDay") fetchDay(dayId: number) {}
|
||||
@Emit("updateLoading") updateLoading(delta: number) {}
|
||||
|
||||
private show = false;
|
||||
private opened = false;
|
||||
private fullyOpened = false;
|
||||
private sidebarOpen = false;
|
||||
private sidebarWidth = 400;
|
||||
private outerWidth = "100vw";
|
||||
|
||||
/** Base dialog */
|
||||
private photoswipe: PhotoSwipe | null = null;
|
||||
|
||||
private list: IPhoto[] = [];
|
||||
private days = new Map<number, IDay>();
|
||||
private dayIds: number[] = [];
|
||||
|
||||
private globalCount = 0;
|
||||
private globalAnchor = -1;
|
||||
|
||||
mounted() {
|
||||
subscribe("files:sidebar:opened", this.handleAppSidebarOpen);
|
||||
subscribe("files:sidebar:closed", this.handleAppSidebarClose);
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
unsubscribe("files:sidebar:opened", this.handleAppSidebarOpen);
|
||||
unsubscribe("files:sidebar:closed", this.handleAppSidebarClose);
|
||||
}
|
||||
|
||||
/** Get the currently open photo */
|
||||
private getCurrentPhoto() {
|
||||
if (!this.list.length || !this.photoswipe) {
|
||||
return null;
|
||||
}
|
||||
const idx = this.photoswipe.currIndex - this.globalAnchor;
|
||||
if (idx < 0 || idx >= this.list.length) {
|
||||
return null;
|
||||
}
|
||||
return this.list[idx];
|
||||
}
|
||||
|
||||
/** Create the base photoswipe object */
|
||||
private async createBase(args: PhotoSwipeOptions) {
|
||||
this.show = true;
|
||||
await this.$nextTick();
|
||||
|
||||
this.photoswipe = new PhotoSwipe({
|
||||
counter: true,
|
||||
zoom: false,
|
||||
loop: false,
|
||||
bgOpacity: 1,
|
||||
appendToEl: this.$refs.inner as HTMLElement,
|
||||
preload: [2, 2],
|
||||
getViewportSizeFn: () => {
|
||||
const sidebarWidth = this.sidebarOpen ? this.sidebarWidth : 0;
|
||||
this.outerWidth = `calc(100vw - ${sidebarWidth}px)`;
|
||||
return {
|
||||
x: window.innerWidth - sidebarWidth,
|
||||
y: window.innerHeight,
|
||||
};
|
||||
},
|
||||
...args,
|
||||
});
|
||||
|
||||
// Debugging only
|
||||
globalThis.photoswipe = this.photoswipe;
|
||||
|
||||
// Monkey patch for focus trapping in sidebar
|
||||
const _onFocusIn = this.photoswipe.keyboard._onFocusIn;
|
||||
this.photoswipe.keyboard._onFocusIn = (e: FocusEvent) => {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
if (
|
||||
e.target.closest("aside.app-sidebar") ||
|
||||
e.target.closest(".v-popper__popper")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_onFocusIn.call(this.photoswipe.keyboard, e);
|
||||
};
|
||||
|
||||
// Refresh sidebar on change
|
||||
this.photoswipe.on("change", () => {
|
||||
if (this.sidebarOpen) {
|
||||
this.openSidebar();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure buttons are styled properly
|
||||
this.photoswipe.addFilter("uiElement", (element, data) => {
|
||||
// add button-vue class if button
|
||||
if (element.classList.contains("pswp__button")) {
|
||||
element.classList.add("button-vue");
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
// Total number of photos in this view
|
||||
this.photoswipe.addFilter("numItems", (numItems) => {
|
||||
return this.globalCount;
|
||||
});
|
||||
|
||||
// Put viewer over everything else
|
||||
const navElem = document.getElementById("app-navigation-vue");
|
||||
const klass = "has-viewer";
|
||||
this.photoswipe.on("beforeOpen", () => {
|
||||
document.body.classList.add(klass);
|
||||
navElem.style.zIndex = "0";
|
||||
});
|
||||
this.photoswipe.on("openingAnimationStart", () => {
|
||||
this.fullyOpened = false;
|
||||
this.opened = true;
|
||||
if (this.sidebarOpen) {
|
||||
this.openSidebar();
|
||||
}
|
||||
});
|
||||
this.photoswipe.on("openingAnimationEnd", () => {
|
||||
this.fullyOpened = true;
|
||||
});
|
||||
this.photoswipe.on("close", () => {
|
||||
this.fullyOpened = false;
|
||||
this.opened = false;
|
||||
this.hideSidebar();
|
||||
});
|
||||
this.photoswipe.on("tapAction", () => {
|
||||
this.opened = !this.opened; // toggle-controls
|
||||
});
|
||||
this.photoswipe.on("destroy", () => {
|
||||
document.body.classList.remove(klass);
|
||||
navElem.style.zIndex = "";
|
||||
|
||||
// reset everything
|
||||
this.show = false;
|
||||
this.opened = false;
|
||||
this.fullyOpened = false;
|
||||
this.photoswipe = null;
|
||||
this.list = [];
|
||||
this.days.clear();
|
||||
this.dayIds = [];
|
||||
this.globalCount = 0;
|
||||
this.globalAnchor = -1;
|
||||
});
|
||||
|
||||
// Video support
|
||||
this.photoswipe.on("contentLoad", (e) => {
|
||||
const { content, isLazy } = e;
|
||||
if (content.data.photo.flag & this.c.FLAG_IS_VIDEO) {
|
||||
e.preventDefault();
|
||||
|
||||
content.type = "video";
|
||||
|
||||
// Create video element
|
||||
content.videoElement = document.createElement("video") as any;
|
||||
content.videoElement.classList.add("video-js");
|
||||
|
||||
// Get DAV URL for video
|
||||
let url = `remote.php/dav/${content.data.photo.filename}`; // normal route
|
||||
// Check if albums
|
||||
const route = vuerouter.currentRoute;
|
||||
if (route.name === "albums") {
|
||||
const fInfos = getAlbumFileInfos(
|
||||
[content.data.photo],
|
||||
route.params.user,
|
||||
route.params.name
|
||||
);
|
||||
if (fInfos.length) {
|
||||
url = `remote.php/dav/${fInfos[0].originalFilename}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add child with source element
|
||||
const source = document.createElement("source");
|
||||
source.src = generateUrl(url);
|
||||
source.type = content.data.photo.mimetype;
|
||||
content.videoElement.appendChild(source);
|
||||
|
||||
// Create container div
|
||||
content.element = document.createElement("div");
|
||||
content.element.appendChild(content.videoElement);
|
||||
|
||||
// Init videojs
|
||||
videojs(content.videoElement, {
|
||||
fluid: true,
|
||||
autoplay: content.data.playvideo,
|
||||
controls: true,
|
||||
preload: "metadata",
|
||||
muted: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
withCredentials: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Play video on open slide
|
||||
this.photoswipe.on("slideActivate", (e) => {
|
||||
const { slide } = e;
|
||||
if (slide.data.photo.flag & this.c.FLAG_IS_VIDEO) {
|
||||
setTimeout(() => {
|
||||
slide.content.element.querySelector("video")?.play();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Pause video on close slide
|
||||
this.photoswipe.on("slideDeactivate", (e) => {
|
||||
const { slide } = e;
|
||||
if (slide.data.photo.flag & this.c.FLAG_IS_VIDEO) {
|
||||
slide.content.element.querySelector("video")?.pause();
|
||||
}
|
||||
});
|
||||
|
||||
return this.photoswipe;
|
||||
}
|
||||
|
||||
/** Open using start photo and rows list */
|
||||
public async open(anchorPhoto: IPhoto, rows?: IRow[]) {
|
||||
this.list = [...anchorPhoto.d.detail];
|
||||
let startIndex = -1;
|
||||
|
||||
for (const r of rows) {
|
||||
if (r.type === IRowType.HEAD) {
|
||||
if (r.day.dayid == anchorPhoto.d.dayid) {
|
||||
startIndex = r.day.detail.findIndex(
|
||||
(p) => p.fileid === anchorPhoto.fileid
|
||||
);
|
||||
this.globalAnchor = this.globalCount;
|
||||
}
|
||||
|
||||
this.globalCount += r.day.count;
|
||||
this.days.set(r.day.dayid, r.day);
|
||||
this.dayIds.push(r.day.dayid);
|
||||
}
|
||||
}
|
||||
|
||||
await this.createBase({
|
||||
index: this.globalAnchor + startIndex,
|
||||
});
|
||||
|
||||
this.photoswipe.addFilter("itemData", (itemData, index) => {
|
||||
// Get photo object from list
|
||||
let idx = index - this.globalAnchor;
|
||||
if (idx < 0) {
|
||||
// Load previous day
|
||||
const firstDayId = this.list[0].d.dayid;
|
||||
const firstDayIdx = utils.binarySearch(this.dayIds, firstDayId);
|
||||
if (firstDayIdx === 0) {
|
||||
// No previous day
|
||||
return {};
|
||||
}
|
||||
const prevDayId = this.dayIds[firstDayIdx - 1];
|
||||
const prevDay = this.days.get(prevDayId);
|
||||
if (!prevDay.detail) {
|
||||
console.error("[BUG] No detail for previous day");
|
||||
return {};
|
||||
}
|
||||
this.list.unshift(...prevDay.detail);
|
||||
this.globalAnchor -= prevDay.count;
|
||||
} else if (idx >= this.list.length) {
|
||||
// Load next day
|
||||
const lastDayId = this.list[this.list.length - 1].d.dayid;
|
||||
const lastDayIdx = utils.binarySearch(this.dayIds, lastDayId);
|
||||
if (lastDayIdx === this.dayIds.length - 1) {
|
||||
// No next day
|
||||
return {};
|
||||
}
|
||||
const nextDayId = this.dayIds[lastDayIdx + 1];
|
||||
const nextDay = this.days.get(nextDayId);
|
||||
if (!nextDay.detail) {
|
||||
console.error("[BUG] No detail for next day");
|
||||
return {};
|
||||
}
|
||||
this.list.push(...nextDay.detail);
|
||||
}
|
||||
|
||||
idx = index - this.globalAnchor;
|
||||
const photo = this.list[idx];
|
||||
|
||||
// Something went really wrong
|
||||
if (!photo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Preload next and previous 3 days
|
||||
const dayIdx = utils.binarySearch(this.dayIds, photo.d.dayid);
|
||||
const preload = (idx: number) => {
|
||||
if (
|
||||
idx > 0 &&
|
||||
idx < this.dayIds.length &&
|
||||
!this.days.get(this.dayIds[idx]).detail
|
||||
) {
|
||||
this.fetchDay(this.dayIds[idx]);
|
||||
}
|
||||
};
|
||||
preload(dayIdx - 1);
|
||||
preload(dayIdx - 2);
|
||||
preload(dayIdx - 3);
|
||||
preload(dayIdx + 1);
|
||||
preload(dayIdx + 2);
|
||||
preload(dayIdx + 3);
|
||||
|
||||
// Get thumb image
|
||||
const thumbSrc: string =
|
||||
photo.flag & this.c.FLAG_IS_VIDEO
|
||||
? undefined
|
||||
: this.thumbElem(photo)?.querySelector("img")?.getAttribute("src") ||
|
||||
getPreviewUrl(photo, false, 256);
|
||||
|
||||
// Get full image
|
||||
return {
|
||||
...this.getItemData(photo),
|
||||
msrc: thumbSrc,
|
||||
};
|
||||
});
|
||||
|
||||
this.photoswipe.addFilter("thumbEl", (thumbEl, data, index) => {
|
||||
const photo = this.list[index - this.globalAnchor];
|
||||
if (photo.flag & this.c.FLAG_IS_VIDEO) return thumbEl;
|
||||
return this.thumbElem(photo) || thumbEl;
|
||||
});
|
||||
|
||||
this.photoswipe.init();
|
||||
}
|
||||
|
||||
/** Open with a static list of photos */
|
||||
public async openStatic(photo: IPhoto, list: IPhoto[]) {
|
||||
this.list = list;
|
||||
await this.createBase({
|
||||
index: list.findIndex((p) => p.fileid === photo.fileid),
|
||||
});
|
||||
|
||||
this.globalCount = list.length;
|
||||
this.globalAnchor = 0;
|
||||
|
||||
this.photoswipe.addFilter("itemData", (itemData, index) => {
|
||||
return this.getItemData(this.list[index]);
|
||||
});
|
||||
|
||||
this.photoswipe.init();
|
||||
}
|
||||
|
||||
/** Get base data object */
|
||||
private getItemData(photo: IPhoto) {
|
||||
return {
|
||||
src: getPreviewUrl(photo, false, 2048),
|
||||
width: photo.w || undefined,
|
||||
height: photo.h || undefined,
|
||||
thumbCropped: true,
|
||||
photo: photo,
|
||||
};
|
||||
}
|
||||
|
||||
/** Get element for thumbnail if it exists */
|
||||
private thumbElem(photo: IPhoto) {
|
||||
if (!photo) return;
|
||||
return document.getElementById(
|
||||
`memories-photo-${photo.key || photo.fileid}`
|
||||
);
|
||||
}
|
||||
|
||||
/** Delete this photo and refresh */
|
||||
private async deleteCurrent() {
|
||||
const idx = this.photoswipe.currIndex - this.globalAnchor;
|
||||
|
||||
// Delete with WebDAV
|
||||
try {
|
||||
this.updateLoading(1);
|
||||
for await (const p of dav.deletePhotos([this.list[idx]])) {
|
||||
if (!p[0]) return;
|
||||
}
|
||||
} finally {
|
||||
this.updateLoading(-1);
|
||||
}
|
||||
|
||||
const spliced = this.list.splice(idx, 1);
|
||||
this.globalCount--;
|
||||
for (let i = idx - 3; i <= idx + 3; i++) {
|
||||
this.photoswipe.refreshSlideContent(i + this.globalAnchor);
|
||||
}
|
||||
this.deleted(spliced);
|
||||
}
|
||||
|
||||
/** Is the current photo a favorite */
|
||||
private isFavorite() {
|
||||
const p = this.getCurrentPhoto();
|
||||
if (!p) return false;
|
||||
return Boolean(p.flag & this.c.FLAG_IS_FAVORITE);
|
||||
}
|
||||
|
||||
/** Favorite the current photo */
|
||||
private async favoriteCurrent() {
|
||||
const photo = this.getCurrentPhoto();
|
||||
const val = !this.isFavorite();
|
||||
try {
|
||||
this.updateLoading(1);
|
||||
for await (const p of dav.favoritePhotos([photo], val)) {
|
||||
if (!p[0]) return;
|
||||
this.$forceUpdate();
|
||||
}
|
||||
} finally {
|
||||
this.updateLoading(-1);
|
||||
}
|
||||
|
||||
// Set flag on success
|
||||
if (val) {
|
||||
photo.flag |= this.c.FLAG_IS_FAVORITE;
|
||||
} else {
|
||||
photo.flag &= ~this.c.FLAG_IS_FAVORITE;
|
||||
}
|
||||
}
|
||||
|
||||
/** Download the current photo */
|
||||
private async downloadCurrent() {
|
||||
const photo = this.getCurrentPhoto();
|
||||
if (!photo) return;
|
||||
dav.downloadFilesByIds([photo]);
|
||||
}
|
||||
|
||||
/** Open the sidebar */
|
||||
private async openSidebar(photo?: IPhoto) {
|
||||
const fInfo = await dav.getFiles([photo || this.getCurrentPhoto()]);
|
||||
globalThis.OCA?.Files?.Sidebar?.setFullScreenMode?.(true);
|
||||
globalThis.OCA.Files.Sidebar.open(fInfo[0].filename);
|
||||
}
|
||||
|
||||
private async updateSizeWithoutAnim() {
|
||||
const wasFullyOpened = this.fullyOpened;
|
||||
this.fullyOpened = false;
|
||||
this.photoswipe.updateSize();
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
this.fullyOpened = wasFullyOpened;
|
||||
}
|
||||
|
||||
private handleAppSidebarOpen() {
|
||||
if (this.show && this.photoswipe) {
|
||||
const sidebar: HTMLElement = document.querySelector("aside.app-sidebar");
|
||||
if (sidebar) {
|
||||
this.sidebarWidth = sidebar.offsetWidth - 2;
|
||||
}
|
||||
|
||||
this.sidebarOpen = true;
|
||||
this.updateSizeWithoutAnim();
|
||||
}
|
||||
}
|
||||
|
||||
private handleAppSidebarClose() {
|
||||
if (this.show && this.photoswipe && this.fullyOpened) {
|
||||
this.sidebarOpen = false;
|
||||
this.updateSizeWithoutAnim();
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the sidebar, without marking it as closed */
|
||||
private hideSidebar() {
|
||||
globalThis.OCA?.Files?.Sidebar?.close();
|
||||
globalThis.OCA?.Files?.Sidebar?.setFullScreenMode?.(false);
|
||||
}
|
||||
|
||||
/** Close the sidebar */
|
||||
private closeSidebar() {
|
||||
this.hideSidebar();
|
||||
this.sidebarOpen = false;
|
||||
this.photoswipe.updateSize();
|
||||
}
|
||||
|
||||
/** Toggle the sidebar visibility */
|
||||
private toggleSidebar() {
|
||||
if (this.sidebarOpen) {
|
||||
this.closeSidebar();
|
||||
} else {
|
||||
this.openSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the files app with the current file.
|
||||
*/
|
||||
private async viewInFolder() {
|
||||
const photo = this.getCurrentPhoto();
|
||||
if (photo) dav.viewInFolder(photo);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.outer {
|
||||
z-index: 3000;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
z-index: 100001;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 50px;
|
||||
|
||||
:deep .button-vue--icon-only {
|
||||
color: white;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
&.opened {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fullyOpened :deep .pswp__container {
|
||||
@media (min-width: 1024px) {
|
||||
transition: transform var(--pswp-transition-duration) ease !important;
|
||||
}
|
||||
}
|
||||
|
||||
.inner,
|
||||
.inner :deep .pswp {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
:deep .video-js .vjs-big-play-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep .pswp {
|
||||
.pswp__zoom-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pswp__button {
|
||||
color: white;
|
||||
|
||||
&,
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.pswp__icn-shadow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
:id="`memories-photo-${data.key || data.fileid}`"
|
||||
class="p-outer fill-block"
|
||||
:class="{
|
||||
selected: data.flag & c.FLAG_SELECTED,
|
||||
|
|
|
@ -47,7 +47,6 @@ import { NcActions, NcActionButton } from "@nextcloud/vue";
|
|||
|
||||
import * as utils from "../../services/Utils";
|
||||
import * as dav from "../../services/DavRequests";
|
||||
import { ViewerManager } from "../../services/Viewer";
|
||||
import { IPhoto } from "../../types";
|
||||
import { getPreviewUrl } from "../../services/FileUtils";
|
||||
|
||||
|
@ -74,6 +73,9 @@ interface IYear {
|
|||
export default class OnThisDay extends Mixins(GlobalMixin) {
|
||||
private getPreviewUrl = getPreviewUrl;
|
||||
|
||||
@Prop()
|
||||
private viewer: any;
|
||||
|
||||
@Emit("load")
|
||||
onload() {}
|
||||
|
||||
|
@ -83,14 +85,6 @@ export default class OnThisDay extends Mixins(GlobalMixin) {
|
|||
private hasLeft = false;
|
||||
private scrollStack: number[] = [];
|
||||
|
||||
/**
|
||||
* Nextcloud viewer proxy
|
||||
* Can't use the timeline instance because these photos
|
||||
* might not be in view, so can't delete them
|
||||
*/
|
||||
@Prop()
|
||||
private viewerManager!: ViewerManager;
|
||||
|
||||
mounted() {
|
||||
const inner = this.$refs.inner as HTMLElement;
|
||||
inner.addEventListener("scroll", this.onScroll.bind(this), {
|
||||
|
@ -200,7 +194,7 @@ export default class OnThisDay extends Mixins(GlobalMixin) {
|
|||
|
||||
click(year: IYear) {
|
||||
const allPhotos = this.years.flatMap((y) => y.photos);
|
||||
this.viewerManager.open(year.preview, allPhotos);
|
||||
this.viewer.openStatic(year.preview, allPhotos);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from "./dav/favorites";
|
|||
export * from "./dav/folders";
|
||||
export * from "./dav/onthisday";
|
||||
export * from "./dav/tags";
|
||||
export * from "./dav/other";
|
||||
|
|
|
@ -51,6 +51,15 @@ export function getLongDateStr(date: Date, skipYear = false, time = false) {
|
|||
});
|
||||
}
|
||||
|
||||
/** Get month and year string */
|
||||
export function getMonthDateStr(date: Date) {
|
||||
return date.toLocaleDateString(getCanonicalLocale(), {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
}
|
||||
|
||||
/** Get text like "5 years ago" from a date */
|
||||
export function getFromNowStr(date: Date) {
|
||||
// Get fromNow in correct locale
|
||||
|
@ -85,6 +94,12 @@ export function hashCode(str: string): number {
|
|||
* @param key Key to use for comparison
|
||||
*/
|
||||
export function binarySearch(arr: any, elem: any, key?: string) {
|
||||
if (arr.length === 0) return 0;
|
||||
|
||||
const desc = key
|
||||
? arr[0][key] > arr[arr.length - 1][key]
|
||||
: arr[0] > arr[arr.length - 1];
|
||||
|
||||
let minIndex = 0;
|
||||
let maxIndex = arr.length - 1;
|
||||
let currentIndex: number;
|
||||
|
@ -94,9 +109,12 @@ export function binarySearch(arr: any, elem: any, key?: string) {
|
|||
currentIndex = ((minIndex + maxIndex) / 2) | 0;
|
||||
currentElement = key ? arr[currentIndex][key] : arr[currentIndex];
|
||||
|
||||
if (currentElement < elem) {
|
||||
const e1 = desc ? elem : currentElement;
|
||||
const e2 = desc ? currentElement : elem;
|
||||
|
||||
if (e1 < e2) {
|
||||
minIndex = currentIndex + 1;
|
||||
} else if (currentElement > elem) {
|
||||
} else if (e1 > e2) {
|
||||
maxIndex = currentIndex - 1;
|
||||
} else {
|
||||
return currentIndex;
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import { IFileInfo, IPhoto } from "../types";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { subscribe } from "@nextcloud/event-bus";
|
||||
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||
import { Route } from "vue-router";
|
||||
import * as dav from "./DavRequests";
|
||||
|
||||
// Key to store sidebar state
|
||||
const SIDEBAR_KEY = "memories:sidebar-open";
|
||||
|
||||
export class ViewerManager {
|
||||
/** Map from fileid to Photo */
|
||||
private photoMap = new Map<number, IPhoto>();
|
||||
|
||||
constructor(
|
||||
ondelete: (photos: IPhoto[]) => void,
|
||||
private updateLoading: (delta: number) => void,
|
||||
private $route: Route
|
||||
) {
|
||||
subscribe("files:file:deleted", ({ fileid }: { fileid: number }) => {
|
||||
const photo = this.photoMap.get(fileid);
|
||||
ondelete([photo]);
|
||||
});
|
||||
}
|
||||
|
||||
public async open(photo: IPhoto, list?: IPhoto[]) {
|
||||
list = list || photo.d?.detail;
|
||||
if (!list?.length) return;
|
||||
|
||||
// Repopulate map
|
||||
this.photoMap.clear();
|
||||
for (const p of list) {
|
||||
this.photoMap.set(p.fileid, p);
|
||||
}
|
||||
|
||||
// Get file infos
|
||||
let fileInfos: IFileInfo[];
|
||||
try {
|
||||
this.updateLoading(1);
|
||||
fileInfos = await dav.getFiles(list);
|
||||
} catch (e) {
|
||||
console.error("Failed to load fileInfos", e);
|
||||
showError("Failed to load fileInfos");
|
||||
return;
|
||||
} finally {
|
||||
this.updateLoading(-1);
|
||||
}
|
||||
if (fileInfos.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix sorting of the fileInfos
|
||||
const itemPositions = {};
|
||||
for (const [index, p] of list.entries()) {
|
||||
itemPositions[p.fileid] = index;
|
||||
}
|
||||
fileInfos.sort(function (a, b) {
|
||||
return itemPositions[a.fileid] - itemPositions[b.fileid];
|
||||
});
|
||||
|
||||
// Get this photo in the fileInfos
|
||||
const fInfo = fileInfos.find((d) => Number(d.fileid) === photo.fileid);
|
||||
if (!fInfo) {
|
||||
showError(t("memories", "Cannot find this photo anymore!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check viewer > 2.0.0
|
||||
const viewerVersion: string = globalThis.OCA.Viewer.version;
|
||||
const viewerMajor = Number(viewerVersion.split(".")[0]);
|
||||
|
||||
// Open Nextcloud viewer
|
||||
globalThis.OCA.Viewer.open({
|
||||
fileInfo: fInfo,
|
||||
path: viewerMajor < 2 ? fInfo.filename : undefined, // Only specify path upto Nextcloud 24
|
||||
list: fileInfos, // file list
|
||||
canLoop: false, // don't loop
|
||||
onClose: () => {
|
||||
// on viewer close
|
||||
if (globalThis.OCA.Files.Sidebar.file) {
|
||||
localStorage.setItem(SIDEBAR_KEY, "1");
|
||||
} else {
|
||||
localStorage.removeItem(SIDEBAR_KEY);
|
||||
}
|
||||
globalThis.OCA.Files.Sidebar.close();
|
||||
},
|
||||
});
|
||||
|
||||
// Restore sidebar state
|
||||
if (localStorage.getItem(SIDEBAR_KEY) === "1") {
|
||||
globalThis.OCA.Files.Sidebar.open(fInfo.filename);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,6 +62,7 @@ export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
|
|||
mime: photo.mimetype,
|
||||
hasPreview: true,
|
||||
etag: photo.etag,
|
||||
permissions: "RWD",
|
||||
} as IFileInfo;
|
||||
})
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ import { translate as t } from "@nextcloud/l10n";
|
|||
import { IPhoto } from "../../types";
|
||||
import client from "../DavClient";
|
||||
import * as base from "./base";
|
||||
import * as utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Favorite a file
|
||||
|
@ -61,6 +62,12 @@ export async function* favoritePhotos(
|
|||
const calls = fileInfos.map((fileInfo) => async () => {
|
||||
try {
|
||||
await favoriteFile(fileInfo.originalFilename, favoriteState);
|
||||
const photo = photos.find((p) => p.fileid === fileInfo.fileid);
|
||||
if (favoriteState) {
|
||||
photo.flag |= utils.constants.c.FLAG_IS_FAVORITE;
|
||||
} else {
|
||||
photo.flag &= ~utils.constants.c.FLAG_IS_FAVORITE;
|
||||
}
|
||||
return fileInfo.fileid as number;
|
||||
} catch (error) {
|
||||
console.error("Failed to favorite", fileInfo, error);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { getFiles } from "./base";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { IPhoto } from "../../types";
|
||||
|
||||
/**
|
||||
* Open the files app with the given photo
|
||||
* Opens a new window.
|
||||
*/
|
||||
export async function viewInFolder(photo: IPhoto) {
|
||||
const f = await getFiles([photo]);
|
||||
if (f.length === 0) return;
|
||||
|
||||
const file = f[0];
|
||||
const dirPath = file.filename.split("/").slice(0, -1).join("/");
|
||||
const url = generateUrl(
|
||||
`/apps/files/?dir=${dirPath}&scrollto=${file.fileid}&openfile=${file.fileid}`
|
||||
);
|
||||
window.open(url, "_blank");
|
||||
}
|
|
@ -19,6 +19,8 @@ export type IFileInfo = {
|
|||
flag?: number;
|
||||
/** MIME type of file */
|
||||
mime?: string;
|
||||
/** WebDAV permissions string */
|
||||
permissions?: string;
|
||||
};
|
||||
|
||||
export type IDay = {
|
||||
|
|
Loading…
Reference in New Issue