From e85b8b87baf736d249b9e2e1f43afc6e92ff60d4 Mon Sep 17 00:00:00 2001 From: huangjx Date: Fri, 29 Jul 2022 21:09:08 +0800 Subject: [PATCH] revamped settings frontend --- lib/Settings/Admin.php | 32 ++++--- lib/Settings/Personal.php | 40 +++++---- package.json | 9 +- src/adminSettings.vue | 115 +++++++++++++++++++++++++ src/components/customOptions.vue | 132 +++++++++++++++++++++++++++++ src/components/editableRow.vue | 34 ++++++++ src/components/settingsRow.vue | 72 ++++++++++++++++ src/css/settings.scss | 36 ++++---- src/index.js | 2 - src/lib/settingsForm.ts | 41 +++++---- src/personalSettings.vue | 123 +++++++++++++++++++++++++++ src/settings.js | 140 ++----------------------------- src/utils/helper.js | 84 +++++++++++++++---- templates/settings/Admin.php | 34 +------- templates/settings/Personal.php | 91 +------------------- 15 files changed, 648 insertions(+), 337 deletions(-) create mode 100644 src/adminSettings.vue create mode 100644 src/components/customOptions.vue create mode 100644 src/components/editableRow.vue create mode 100644 src/components/settingsRow.vue create mode 100644 src/personalSettings.vue diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index a352bb2..662591a 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -9,7 +9,8 @@ use OCP\IDBConnection; use OCP\Settings\ISettings; use OCA\NCDownloader\Db\Settings; -class Admin implements ISettings { +class Admin implements ISettings +{ /** @var IDBConnection */ private $connection; @@ -18,9 +19,11 @@ class Admin implements ISettings { /** @var IConfig */ private $config; - public function __construct(IDBConnection $connection, - ITimeFactory $timeFactory, - IConfig $config) { + public function __construct( + IDBConnection $connection, + ITimeFactory $timeFactory, + IConfig $config + ) { $this->connection = $connection; $this->timeFactory = $timeFactory; $this->config = $config; @@ -31,13 +34,18 @@ class Admin implements ISettings { /** * @return TemplateResponse */ - public function getForm() { + public function getForm() + { $this->settings->setType($this->settings::TYPE['SYSTEM']); $parameters = [ - "path" => "/apps/ncdownloader/admin/save", - "ncd_yt_binary" => $this->settings->get("ncd_yt_binary"), - "ncd_aria2_binary" => $this->settings->get("ncd_aria2_binary"), - "ncd_rpctoken" => $this->settings->get("ncd_rpctoken"), + 'settings' => [ + "path" => "/apps/ncdownloader/admin/save", + "ncd_yt_binary" => $this->settings->get("ncd_yt_binary"), + "ncd_aria2_binary" => $this->settings->get("ncd_aria2_binary"), + "ncd_rpctoken" => $this->settings->get("ncd_rpctoken"), + "ncd_aria2_rpc_host" => $this->settings->get("ncd_aria2_rpc_host"), + "ncd_aria2_rpc_port" => $this->settings->get("ncd_aria2_rpc_port"), + ] ]; return new TemplateResponse('ncdownloader', 'settings/Admin', $parameters, ''); } @@ -45,7 +53,8 @@ class Admin implements ISettings { /** * @return string the section ID, e.g. 'sharing' */ - public function getSection(): string { + public function getSection(): string + { return 'ncdownloader'; } @@ -56,7 +65,8 @@ class Admin implements ISettings { * * E.g.: 70 */ - public function getPriority(): int { + public function getPriority(): int + { return 0; } } diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index db8c31f..68f663f 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -10,7 +10,8 @@ use OCP\Settings\ISettings; use OCA\NCDownloader\Db\Settings; use OCA\NCDownloader\Tools\Helper; -class Personal implements ISettings { +class Personal implements ISettings +{ /** @var IDBConnection */ private $connection; @@ -19,39 +20,45 @@ class Personal implements ISettings { /** @var IConfig */ private $config; - public function __construct(IDBConnection $connection, - ITimeFactory $timeFactory, - IConfig $config) { + public function __construct( + IDBConnection $connection, + ITimeFactory $timeFactory, + IConfig $config + ) { $this->connection = $connection; $this->timeFactory = $timeFactory; $this->config = $config; - $this->UserId = \OC::$server->getUserSession()->getUser()->getUID(); - $this->settings = new Settings($this->UserId); + $this->UserId = \OC::$server->getUserSession()->getUser()->getUID(); + $this->settings = new Settings($this->UserId); } /** * @return TemplateResponse */ - public function getForm() { + public function getForm() + { $parameters = [ - "ncd_downloader_dir" => Helper::getDownloadDir(), - "ncd_torrents_dir" => $this->settings->get("ncd_torrents_dir"), - "ncd_seed_ratio" => $this->settings->get("ncd_seed_ratio"), - 'ncd_seed_time_unit' => $this->settings->get("ncd_seed_time_unit"), - 'ncd_seed_time' => $this->settings->get("ncd_seed_time"), - "path" => '/apps/ncdownloader/personal/save', + "settings" => [ + "ncd_downloader_dir" => Helper::getDownloadDir(), + "ncd_torrents_dir" => $this->settings->get("ncd_torrents_dir"), + "ncd_seed_ratio" => $this->settings->get("ncd_seed_ratio"), + 'ncd_seed_time_unit' => $this->settings->get("ncd_seed_time_unit"), + 'ncd_seed_time' => $this->settings->get("ncd_seed_time"), + "path" => '/apps/ncdownloader/personal/save', + ] ]; //\OC_Util::addScript($this->appName, 'common'); //\OC_Util::addScript($this->appName, 'settings/personal'); - //file_put_contents("/tmp/re.log",print_r($parameters,true)); + //file_put_contents("/tmp/re.log",print_r($parameters,true)); return new TemplateResponse('ncdownloader', 'settings/Personal', $parameters, ''); } /** * @return string the section ID, e.g. 'sharing' */ - public function getSection(): string { + public function getSection(): string + { return 'ncdownloader'; } @@ -62,7 +69,8 @@ class Personal implements ISettings { * * E.g.: 70 */ - public function getPriority(): int { + public function getPriority(): int + { return 100; } } diff --git a/package.json b/package.json index 53400d7..f218afa 100755 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "dependencies": { "chalk": "^5.0.0", "toastr": "^2.1.4", - "v-tooltip": "^4.0.0-alpha.1" + "v-tooltip": "^4.0.0-alpha.1", + "vuex": "^4.0.2" }, "engines": { "node": ">=14.0.0", @@ -52,6 +53,7 @@ "css-loader": "^6.4.0", "html-webpack-plugin": "^5.3.2", "jquery": "^3.6.0", + "mini-css-extract-plugin": "^2.6.0", "popper.js": "^1.16.1", "sass": "^1.38.0", "sass-loader": "^10.2.0", @@ -68,7 +70,6 @@ "vue-loader": "^16.0.0-beta.10", "vue-svg-loader": "^0.17.0-beta.2", "webpack": "^5.69.0", - "webpack-cli": "^4.9.0", - "mini-css-extract-plugin": "^2.6.0" + "webpack-cli": "^4.9.0" } -} \ No newline at end of file +} diff --git a/src/adminSettings.vue b/src/adminSettings.vue new file mode 100644 index 0000000..4d2f08a --- /dev/null +++ b/src/adminSettings.vue @@ -0,0 +1,115 @@ + + diff --git a/src/components/customOptions.vue b/src/components/customOptions.vue new file mode 100644 index 0000000..3dbbd99 --- /dev/null +++ b/src/components/customOptions.vue @@ -0,0 +1,132 @@ + + + diff --git a/src/components/editableRow.vue b/src/components/editableRow.vue new file mode 100644 index 0000000..a1ffd07 --- /dev/null +++ b/src/components/editableRow.vue @@ -0,0 +1,34 @@ + + diff --git a/src/components/settingsRow.vue b/src/components/settingsRow.vue new file mode 100644 index 0000000..526e72b --- /dev/null +++ b/src/components/settingsRow.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/css/settings.scss b/src/css/settings.scss index fb49134..ba06ba1 100644 --- a/src/css/settings.scss +++ b/src/css/settings.scss @@ -1,34 +1,36 @@ .ncdownloader-personal-settings, .ncdownloader-admin-settings { position: relative; + display: flex; + flex-direction: column; + margin-left: 1em; #ncdownloader-message-banner { - position : fixed; - top : 50px; - text-align : center; - padding : 15px; - margin-bottom : 20px; - border : 1px solid transparent; - border-radius : 4px; - text-shadow : 0 1px 0 rgba(255, 255, 255, .2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); - box-shadow : inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); - width : 100%; + position: fixed; + top: 50px; + text-align: center; + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); + width: 100%; background-color: #dff0d8; } #ncdownloader-message-banner.success, .message-banner.success { - color : #799479; + color: #799479; background-color: #dff0d8; - border-color : #d6e9c6; + border-color: #d6e9c6; } #ncdownloader-message-banner.error, .message-banner.error { - color : #a94442; + color: #a94442; background-color: #f2dede; - border-color : #ebccd1; + border-color: #ebccd1; } - -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index 5931d97..04eccf0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ import helper from './utils/helper' import eventHandler from './lib/eventHandler' -import Http from './lib/http' import { translate as t, translatePlural as n } from '@nextcloud/l10n' import updatePage from './actions/updatePage' import buttonActions from './actions/buttonActions' @@ -10,7 +9,6 @@ import { createApp } from 'vue' import App from './App'; import tippy, { delegate } from 'tippy.js'; import 'tippy.js/dist/tippy.css'; -'use strict' import settingsBar from './settingsBar'; const basePath = "/apps/ncdownloader"; diff --git a/src/lib/settingsForm.ts b/src/lib/settingsForm.ts index 2f61461..ddc4ecd 100644 --- a/src/lib/settingsForm.ts +++ b/src/lib/settingsForm.ts @@ -8,31 +8,30 @@ type dataItems = { type data = Array class settingsForm { - parent = "custom-aria2-settings-container"; - constructor() { - + container; + constructor(containerId?: string) { + this.container = containerId } - static getInstance() { - return new this(); + static getInstance(containerId?: string) { + return new this(containerId); } - setParent(selector: string): settingsForm { - this.parent = selector; + setContainer(selector: string): settingsForm { + this.container = selector; return this; } - create(parent: HTMLElement, element: dataItems) { + create(containerEle: HTMLElement, element: dataItems) { let label = this._createLabel(element.name, element.id) let input = this._createInput(element); //let saveBtn = this._createSaveBtn(element.id); let cancelBtn = this._createCancelBtn("has-content"); - let container = this._createContainer(element.id); + let wrapper = this._createContainer(element.id); [label, input, cancelBtn].forEach(ele => { - container.appendChild(ele); + wrapper.appendChild(ele); }) - - return parent.prepend(container); + return containerEle.prepend(wrapper); } - createCustomInput(keyId: string, valueId: string): HTMLElement { + createInputGroup(keyId: string, valueId: string): HTMLElement { let div = this._createContainer(keyId + "-container") let items: dataItems = { id: keyId, @@ -48,7 +47,8 @@ class settingsForm { _createContainer(id: string): HTMLElement { let div = document.createElement("div"); - div.classList.add(id); + div.setAttribute("id",id); + div.classList.add("autocomplete-container") return div; } _createCancelBtn(className = ''): HTMLElement { @@ -57,6 +57,10 @@ class settingsForm { button.classList.add(className); //button.setAttribute("type",'button') button.classList.add("icon-close"); + button.addEventListener("click", function () { + let container = this.parentNode as HTMLElement + container.remove() + }) return button; } _createSaveBtn(id: string): HTMLElement { @@ -68,7 +72,7 @@ class settingsForm { } _createLabel(name: string, id: string): HTMLElement { name = name.replace('_', '-'); - let label = document.createElement("lable"); + let label = document.createElement("label"); label.setAttribute("for", id); let text = document.createTextNode(name); label.appendChild(text); @@ -90,9 +94,12 @@ class settingsForm { return input; } render(data: data) { - let parent = document.getElementById(this.parent) + let container = document.getElementById(this.container) + if (!container) { + throw this.container + " is not found" + } for (const element of data) { - this.create(parent, element) + this.create(container, element) } } } diff --git a/src/personalSettings.vue b/src/personalSettings.vue new file mode 100644 index 0000000..ac1c6e0 --- /dev/null +++ b/src/personalSettings.vue @@ -0,0 +1,123 @@ + + diff --git a/src/settings.js b/src/settings.js index e8d8364..00177e4 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1,143 +1,19 @@ -import Http from './lib/http' -import OC_msg from './lib/msg' -import { - generateUrl -} from '@nextcloud/router' -import settingsForm from './lib/settingsForm' -import autoComplete from './lib/autoComplete'; import eventHandler from './lib/eventHandler'; -import aria2Options from './utils/aria2Options'; -import { options as ytdlFullOptions, names as ytdlOptions } from './utils/ytdlOptions'; import helper from './utils/helper'; import './css/autoComplete.css' import './css/settings.scss' -'use strict'; import { delegate } from 'tippy.js'; import 'tippy.js/dist/tippy.css'; +import { createApp } from 'vue'; +import adminSettings from './adminSettings'; +import personalSettings from './personalSettings'; + +const customSettings = createApp(adminSettings) +const pSettings = createApp(personalSettings) +customSettings.mount('#ncdownloader-admin-settings') +pSettings.mount('#ncdownloader-personal-settings') window.addEventListener('DOMContentLoaded', function () { - let customOptions = ['ncd_downloader_dir', 'ncd_torrents_dir', 'ncd_seed_ratio', 'ncd_seed_time', 'ncd_rpctoken', 'ncd_yt_binary', 'ncd_aria2_binary']; - const saveHandler = (e, name) => { - e.stopImmediatePropagation(); - let element = e.target; - let data = helper.getData(element.getAttribute("data-rel")); - let url = generateUrl(data.path); - delete data.path; - OC_msg.startSaving('#ncdownloader-message-banner'); - helper.makePair(data, name); - let badOptions = []; - if (name === 'ytdl-settings') { - for (let key in data) { - if (!ytdlOptions.includes(key) && !customOptions.includes(key)) { - delete data[key]; - badOptions.push(key) - } - } - } else { - for (let key in data) { - if (!aria2Options.includes(key) && !customOptions.includes(key)) { - delete data[key]; - badOptions.push(key) - } - } - } - if (badOptions.length > 0) { - OC_msg.finishedError('#ncdownloader-message-banner', 'invalid options: ' + badOptions.join(',')); - return; - } - helper.httpClient(url).setData(data).setHandler(function (data) { - if (data.hasOwnProperty("error")) { - OC_msg.finishedError('#ncdownloader-message-banner', data.error); - } else if (data.hasOwnProperty("message")) { - OC_msg.finishedSuccess('#ncdownloader-message-banner', data.message); - } else { - OC_msg.finishedSuccess('#ncdownloader-message-banner', "DONE"); - } - }).send(); - } - const addOption = (e, name, options) => { - e.preventDefault(); - e.stopPropagation(); - let baseName = `${name}-settings`; - let element = e.target; - let selector = `#${baseName}-key-1`; - let form = settingsForm.getInstance(); - let nodeList, key, value; - nodeList = document.querySelectorAll(`[id^='${baseName}-key']`) - if (nodeList.length === 0) { - key = `${baseName}-key-1`; - value = `${baseName}-value-1`; - } else { - let index = nodeList.length + 1; - key = `${baseName}-key-${index}`; - value = `${baseName}-value-${index}`; - selector = `[id^='${baseName}-key']`; - } - element.before(form.createCustomInput(key, value)); - try { - autoComplete.getInstance({ - selector: `[id^='${baseName}-key']`, - minChars: 1, - sourceHandler: function () { - if (Array.isArray(options)) { - return options; - } - return Object.keys(options); - }, - renderer: (item, search) => { - search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - let tippy; - if (options.hasOwnProperty(item)) { - tippy = options[item]; - } else { - tippy = item; - } - var re = new RegExp(`(${search.split(' ').join('|')})`, "gi"); - return `
${item.replace(re, "$1")}
`; - } - }).run(); - } catch (error) { - console.log(error) - OC_msg.finishedError('#ncdownloader-message-banner', error); - } - } - - eventHandler.add('click', '.ncdownloader-admin-settings', 'input[type="button"]', (e) => saveHandler(e)); - eventHandler.add('click', '.ncdownloader-personal-settings', 'input[type="button"]', (e) => saveHandler(e)); - eventHandler.add("click", "#custom-aria2-settings-container", "button.save-custom-aria2-settings", (e) => saveHandler(e)) - eventHandler.add("click", "#custom-ytdl-settings-container", "button.save-custom-ytdl-settings", (e) => saveHandler(e, 'ytdl-settings')) - - eventHandler.add('click', '#custom-aria2-settings-container', "button.add-custom-aria2-settings", (e) => addOption(e, 'aria2', aria2Options)) - eventHandler.add('click', '#custom-ytdl-settings-container', "button.add-custom-ytdl-settings", (e) => addOption(e, 'ytdl', ytdlFullOptions)) - - - eventHandler.add('click', '.ncdownloader-personal-settings', 'button.icon-close', function (e) { - e.stopImmediatePropagation(); - e.preventDefault(); - this.parentNode.remove(); - }) - helper.httpClient(generateUrl("/apps/ncdownloader/personal/aria2/get")).setHandler(function (data) { - if (!data) { - return; - } - let input = []; - for (let key in data) { - if (aria2Options.includes(key)) - input.push({ name: key, value: data[key], id: key }); - } - settingsForm.getInstance().render(input); - }).send(); - - helper.httpClient(generateUrl("/apps/ncdownloader/personal/ytdl/get")).setHandler(function (data) { - if (!data) { - return; - } - let input = []; - for (let key in data) { - input.push({ name: key, value: data[key], id: key }); - } - settingsForm.getInstance().setParent("custom-ytdl-settings-container").render(input); - }).send(); const filepicker = function (event) { let element = event.target; diff --git a/src/utils/helper.js b/src/utils/helper.js index c864155..061e7c6 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -7,6 +7,8 @@ import { translate as t, translatePlural as n } from '@nextcloud/l10n' import contentTable from '../lib/contentTable'; import Http from '../lib/http' import Polling from "../lib/polling"; +import autoComplete from '../lib/autoComplete'; + const helper = { vue: {}, addVue(name, object) { @@ -69,9 +71,13 @@ const helper = { return string.charAt(0).toUpperCase() + string.slice(1) }, isURL(url) { - let regex = '^((https?|ftp)://)([a-z0-9-]+\.)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]+)$'; - const pattern = new RegExp(regex, 'i'); - return pattern.test(url); + try { + new URL(url.trim()); + return true; + } catch (e) { + console.log(e.message); + return false; + } }, isMagnetURI(url) { const magnetURI = /^magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32,40}(&dn=.+&tr=.+)?$/i; @@ -154,23 +160,10 @@ const helper = { let doc = parser.parseFromString(htmlString, "text/html") return doc.querySelector("div"); }, - makePair: function (data, prefix = "aria2-settings") { - for (let key in data) { - let index; - if ((index = key.indexOf(prefix + "-key-")) !== -1) { - let valueKey = prefix + "-value-" + key.substring(key.lastIndexOf('-') + 1); - if (data[valueKey] === undefined) continue; - let newkey = data[key]; - data[newkey] = data[valueKey]; - delete data[key]; - delete data[valueKey]; - } - } - }, getData(selector) { const element = typeof selector === "object" ? selector : document.getElementById(selector) const data = {} - data['path'] = element.getAttribute('path') || ''; + data['_path'] = element.getAttribute('path') || ''; //if the targeted element is not of input or select type, search for such elements below it if (!['SELECT', 'INPUT'].includes(element.nodeName.toUpperCase())) { const nodeList = element.querySelectorAll('input,select') @@ -183,11 +176,17 @@ const helper = { const key = element.getAttribute('id') data[key] = element.value for (let prop in element.dataset) { + if (prop == "rel") { + continue; + } data[prop] = element.dataset[prop]; } } } else { for (let prop in element.dataset) { + if (prop == "rel") { + continue; + } data[prop] = element.dataset[prop]; } const key = element.getAttribute('id') @@ -287,7 +286,7 @@ const helper = { container.setAttribute("type", name); container.className = "table " + name; }, - filepicker(cb,currentPath) { + filepicker(cb, currentPath) { OC.dialogs.filepicker( t('ncdownloader', 'Select a directory'), cb, @@ -308,6 +307,55 @@ const helper = { }, httpClient(url) { return new Http.create(url, true) + }, + autoComplete(selector, options) { + try { + autoComplete.getInstance({ + selector: selector, + minChars: 1, + sourceHandler: function () { + if (Array.isArray(options)) { + return options; + } + return Object.keys(options); + }, + renderer: (item, search) => { + if (!item || !search) { + return; + } + search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + let tippy; + if (options.hasOwnProperty(item)) { + tippy = options[item]; + } else { + tippy = item; + } + var re = new RegExp(`(${search.split(' ').join('|')})`, "gi"); + return `
${item.replace(re, "$1")}
`; + } + }).run(); + } catch (error) { + console.log(error) + helper.error(error); + } + }, + transformParams(data, prefix = "aria2-settings") { + let index + for (let key in data) { + if (key.charAt(0) == "_") { + delete data[key] + continue + } + if ((index = key.indexOf(prefix + "-key-")) !== -1) { + let valueKey = prefix + "-value-" + key.substring(key.lastIndexOf('-') + 1); + if (data[valueKey] === undefined) continue; + let newkey = data[key]; + data[newkey] = data[valueKey]; + delete data[key]; + delete data[valueKey]; + } + } + return data } } diff --git a/templates/settings/Admin.php b/templates/settings/Admin.php index 66876bc..dc5a9a3 100644 --- a/templates/settings/Admin.php +++ b/templates/settings/Admin.php @@ -3,36 +3,6 @@ script("ncdownloader", 'appSettings'); style("ncdownloader", 'appSettings'); extract($_); ?> -
- -
-

NCDownloader admin Settings

-
- - - -
-
- - - -
-
- - - -
-
+
+
\ No newline at end of file diff --git a/templates/settings/Personal.php b/templates/settings/Personal.php index 2d47168..e2d977b 100644 --- a/templates/settings/Personal.php +++ b/templates/settings/Personal.php @@ -2,93 +2,8 @@ script("ncdownloader", 'appSettings'); style("ncdownloader", 'appSettings'); extract($_); -$time_map = array('i' => 'minutes', 'h' => 'hours', 'w' => 'weeks', 'd' => 'days', 'y' => 'years'); + ?> -
- -
-
-

- t('NCDownloader Settings'));?> -

-
- - - -
-
- - - -
-
-
-
-

- t('BT Sharing settings'));?> -

-
- - - - -
-
-
- - - - -
-
-
-
-
-

- t('Advanced Settings'));?> -

-
-

- t('Custom Aria2 Settings'));?> -

-
- - -
-
-
-

- t('Custom Youtube-dl Settings'));?> -

-
- - -
-
-
-
+
+
\ No newline at end of file