diff --git a/css/public.css b/css/public.css new file mode 100644 index 00000000..f121ecfd --- /dev/null +++ b/css/public.css @@ -0,0 +1,4 @@ +/* Prevent user interaction on content */ +#imgframe { + pointer-events: none; +} diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 0b9588be..ab7433b7 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -91,8 +91,11 @@ public function setDownloadLimit(string $token, int $limit): Response { $insert = true; } - // Update DB + // Set new limit $shareLimit->setLimit($limit); + // Reset existing counter + $shareLimit->setDownloads(0); + // Update DB if ($insert) { $this->mapper->insert($shareLimit); } else { diff --git a/src/models/DownloadLimitAction.js b/src/models/DownloadLimitAction.js index 7a472c75..6d0d050f 100644 --- a/src/models/DownloadLimitAction.js +++ b/src/models/DownloadLimitAction.js @@ -45,18 +45,21 @@ export default class DownloadLimitAction { data() { return { - icon: 'icon-download', + icon: this._store.loading ? 'icon-loading-small' : 'icon-download', is: this._store.enabled ? ActionInput : null, text: t('files_downloadlimit', 'Download limit'), title: t('files_downloadlimit', 'Download count: {count}', this._store), value: this._store.limit, + disabled: this._store.loading, } } get handlers() { return { - 'update:value': debounce((limit) => { - setDownloadLimit(this._store.token, limit) + 'update:value': debounce(async (limit) => { + this._store.loading = true + await setDownloadLimit(this._store.token, limit) + this._store.loading = false }, 300), } } diff --git a/src/models/LimitEnableAction.js b/src/models/LimitEnableAction.js index 44fb3866..4d2a403e 100644 --- a/src/models/LimitEnableAction.js +++ b/src/models/LimitEnableAction.js @@ -57,6 +57,7 @@ export default class LimitEnableAction { is: ActionCheckbox, text: t('files_downloadlimit', 'Limit downloads'), checked: this._store.enabled, + disabled: this._store.loading, } } diff --git a/src/models/Store.js b/src/models/Store.js index 3052faaa..5903dd9a 100644 --- a/src/models/Store.js +++ b/src/models/Store.js @@ -28,6 +28,7 @@ export default class Store { limit: null, count: null, token: null, + loading: false, } get enabled() { @@ -62,4 +63,12 @@ export default class Store { this._data.token = token } + get loading() { + return this._data.loading + } + + set loading(loading) { + this._data.loading = loading + } + } diff --git a/src/public.js b/src/public.js index e01393ad..9ba3829f 100644 --- a/src/public.js +++ b/src/public.js @@ -20,20 +20,64 @@ * */ import { loadState } from '@nextcloud/initial-state' -import { translatePlural as n } from '@nextcloud/l10n' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' + +import '../css/public.css' const { limit, downloads } = loadState(appName, 'download_limit', { limit: -1, downloads: 0 }) console.debug('[DEBUG]', appName, { limit, downloads }) +let count = limit - downloads +let clicks = 0 + +const updateCounter = function(span) { + if (count === 0) { + span.innerText = t(appName, 'You have reached the maximum amount of downloads allowed') + } else { + span.innerText = n(appName, '1 remaining download allowed', '{count} remaining downloads allowed', count, { count }) + } +} + window.addEventListener('DOMContentLoaded', function() { if (limit > 0) { - const count = limit - downloads const container = document.getElementById('header-primary-action') const span = document.createElement('span') span.style = 'color: var(--color-primary-text); padding: 0 10px;' - span.innerText = n('files_downloadlimit', '1 remaining download allowed', '{count} remaining downloads allowed', count, { count }) + updateCounter(span, count) container.prepend(span) + + // Preventing mouse interaction + document.querySelector('#files-public-content').oncontextmenu = function(event) { + event.preventDefault() + event.stopPropagation() + return false + } + + // Adding double-download warning + const downloadButtons = document.querySelectorAll('a[href*="/download/"]') || [] + new Set(downloadButtons).forEach(button => { + button.addEventListener('click', (event) => { + // Warn about download limits + if (clicks > 0) { + if (!confirm(t(appName, 'This share has a limited number of downloads. Are you sure you want to trigger a new download?'))) { + event.preventDefault() + event.stopPropagation() + return + } + } + + // Handle counts changes + count-- + clicks++ + updateCounter(span, count) + + // Remove the buttons if share is now expired + if (count === 0) { + [...downloadButtons].forEach(button => button.remove()) + } + }) + }) } })