revamped settings frontend

This commit is contained in:
huangjx
2022-07-29 21:09:08 +08:00
parent 9075c8fb50
commit e85b8b87ba
15 changed files with 648 additions and 337 deletions

115
src/adminSettings.vue Normal file
View File

@@ -0,0 +1,115 @@
<template>
<div class="section">
<settingsRow
v-for="(option, key) in optionRows"
v-bind:key="key"
:value="option.value"
:id="option.id"
:label="option.label"
:placeholder="option.placeholder"
:path="option.path"
/>
</div>
<customOptions
name="admin-aria2-settings"
@mounted="render"
title="Global Aria2 Settings"
path="/apps/ncdownloader/admin/aria2/save"
:validOptions="validOptions"
>
<template #save>Save Settings</template>
</customOptions>
</template>
<script>
import customOptions from "./components/customOptions";
import helper from "./utils/helper";
import aria2Options from "./utils/aria2Options";
import settingsRow from "./components/settingsRow";
export default {
name: "adminSettings",
data() {
return {
options: [],
validOptions: aria2Options,
};
},
components: {
customOptions,
settingsRow,
},
methods: {
render(event, $vm) {
helper
.httpClient(helper.generateUrl("/apps/ncdownloader/admin/aria2/get"))
.setMethod("GET")
.setHandler((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($vm.container).render(input);
$vm.options = input;
})
.send();
},
},
computed: {
optionRows() {
return this.options;
},
},
mounted() {
try {
let data = this.$el.parentElement.getAttribute("data-settings");
data = JSON.parse(data);
let path = "/apps/ncdownloader/admin/save";
this.options = [
{
label: "Aria2 RPC Host",
id: "ncd_aria2_rpc_host",
value: data.ncd_aria2_rpc_host,
placeholder: "127.0.0.1",
path: path,
},
{
label: "Aria2 RPC Port",
id: "ncd_aria2_rpc_port",
value: data.ncd_aria2_rpc_port,
placeholder: "6800",
path: path,
},
{
label: "Aria2 RPC Token",
id: "ncd_aria2_rpc_token",
value: data.ncd_aria2_rpc_token,
placeholder: data.ncd_aria2_rpc_token ? data.ncd_aria2_rpc_token : "ncdownloader123",
path: path,
},
{
label: "Youtube-dl binary",
id: "ncd_yt_binary",
value: data.ncd_yt_binary,
placeholder: data.ncd_yt_binary
? data.ncd_yt_binary
: "/usr/local/bin/youtube-dl",
path: path,
},
{
label: "Aria2c binary",
id: "ncd_aria2_binary",
value: data.ncd_aria2_binary,
placeholder: "/usr/local/bin/aria2c",
path: path,
},
];
} catch (e) {
helper.error(e);
}
},
};
</script>

View File

@@ -0,0 +1,132 @@
<template>
<div class="section" :class="[classes]" :id="container">
<h3 class="title">{{ title }}</h3>
<div classs="button-container" :id="id" :path="path">
<editableRow
v-for="(option, key) in rows"
v-bind:key="key"
:value="option.value"
:name="option.name"
:placeholder="option.placeholder"
/>
<button
class="custom-settings-add-btn"
@click.prevent="newOption($event, name)"
data-tippy-content="Add new options"
>
<slot name="add">New Option</slot>
</button>
<button
class="custom-settings-save-btn"
@click.prevent="saveOptions($event)"
:data-rel="id"
>
<slot name="save">Save</slot>
</button>
</div>
</div>
</template>
<script>
import helper from "../utils/helper";
import settingsForm from "../lib/settingsForm";
import editableRow from "./editableRow";
export default {
name: "customOptions",
props: {
path: String,
name: {
type: String,
default: "settings",
},
title: {
type: String,
default: "Custom Settings",
},
classes: String,
validOptions: Array,
options: Array,
},
data() {
return {
id: "custom-" + this.name,
classes: "custom-settings-container",
container: "custom-settings-container",
validOptions: this.validOptions,
options: [],
};
},
components: {
editableRow,
},
computed: {
rows() {
return this.options;
},
},
methods: {
newOption(e, baseName) {
e.stopPropagation();
let element = e.target;
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']`;
}
let form = settingsForm.getInstance();
element.before(form.createInputGroup(key, value));
helper.autoComplete(`[id^='${baseName}-key']`, this.validOptions);
},
saveOptions(e) {
let element = e.target;
let container = element.getAttribute("data-rel");
let data = helper.getData(container);
let url = helper.generateUrl(data._path);
data = helper.transformParams(data, this.name);
let badOptions = [];
for (let name in data) {
if (!this.validOptions.includes(name)) {
badOptions.push(name);
}
}
if (badOptions.length > 0) {
helper.error("invalid options: " + badOptions.join(","));
return;
}
helper
.httpClient(url)
.setData(data)
.setHandler((resp) => {
if (resp.error) {
helper.error(resp.error);
return;
}
this.options = [];
for (let key in data) {
this.options.push({ name: key, value: data[key] });
}
let inputDiv = element.parentElement.querySelectorAll(
`div[id^='${this.name}-key']`
);
if (inputDiv && inputDiv.length > 0) {
inputDiv.forEach((element) => {
element.remove();
});
}
helper.info(resp.message);
})
.send();
},
},
mounted() {
this.$emit("mounted", event, this);
},
};
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,34 @@
<template>
<div :id="name">
<label :for="name">{{ name }}</label>
<input
type="text"
:id="name"
:name="name"
:value="value"
placeholder="Leave empty if no value needed"
class="form-input-text"
/>
<button class="has-content icon-close" @click="remove($event)"></button>
</div>
</template>
<script>
export default {
name: "editableRow",
props: {
name: String,
value: String,
placeholder: String,
},
data() {
return {};
},
methods: {
remove(e) {
let ele = e.target;
let container = ele.closest("div");
container.remove();
},
},
};
</script>

View File

@@ -0,0 +1,72 @@
<template>
<div :class="container" :path="path" :id="container">
<label :for="id">{{ label }}</label>
<input
type="text"
:class="classes"
:id="id"
:name="id"
:value="value"
:placeholder="placeholder"
@blur="saveHandler($event)"
:data-rel="container"
/>
<input
v-if="useBtn"
type="button"
value="save"
:data-rel="container"
@click.prevent="saveHandler($event)"
/>
</div>
</template>
<script>
import helper from "../utils/helper";
export default {
name: "settingsRow",
props: {
label: String,
id: String,
value: String,
placeholder: String,
path: String,
useBtn: {
type: Boolean,
default: false,
},
},
data() {
return {
classes: this.id.replace("_", "-") + "-input",
container: this.id.replace("_", "-") + "-container",
};
},
methods: {
saveHandler(e) {
if (e.type == "blur" && this.useBtn) {
return;
}
e.stopPropagation();
let element = e.target;
let data = helper.getData(element.getAttribute("data-rel"));
let url = helper.generateUrl(data._path);
data = helper.transformParams(data);
helper
.httpClient(url)
.setData(data)
.setHandler(function (resp) {
if (resp.error) {
helper.error(resp.error);
return;
}
helper.info(resp.message);
})
.send();
},
},
computed: {},
mounted() {},
};
</script>
<style scoped></style>

View File

@@ -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;
}
}
}

View File

@@ -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";

View File

@@ -8,31 +8,30 @@ type dataItems = {
type data = Array<dataItems>
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)
}
}
}

123
src/personalSettings.vue Normal file
View File

@@ -0,0 +1,123 @@
<template>
<div class="section ncdownloader-general-settings">
<h3>General Settings</h3>
<settingsRow
v-for="(option, key) in optionRows"
v-bind:key="key"
:value="option.value"
:id="option.id"
:label="option.label"
:placeholder="option.placeholder"
:path="option.path"
:useBtn="true"
/>
</div>
<customOptions
name="custom-aria2-settings"
title="Personal Aria2 Settings"
@mounted="renderAria2"
path="/apps/ncdownloader/personal/aria2/save"
:validOptions="aria2Options"
>
<template #save>Save Aria2 Settings</template>
</customOptions>
<customOptions
name="custom-ytdl-settings"
title="Personal Youtbue-dl Settings"
@mounted="renderYtdl"
path="/apps/ncdownloader/personal/ytdl/save"
:validOptions="ytdlOptions"
>
<template #save>Save Youtube-dl Settings</template>
</customOptions>
</template>
<script>
import customOptions from "./components/customOptions";
import helper from "./utils/helper";
import aria2Options from "./utils/aria2Options";
import { options as ytdlFullOptions, names as ytdlOptions } from "./utils/ytdlOptions";
import settingsRow from "./components/settingsRow";
export default {
name: "personalSettings",
data() {
return {
options: [],
aria2Options: aria2Options,
ytdlOptions: ytdlOptions,
};
},
components: {
customOptions,
settingsRow,
},
methods: {
renderAria2(event, $vm) {
helper
.httpClient(helper.generateUrl("/apps/ncdownloader/personal/aria2/get"))
//.setMethod("GET")
.setHandler((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($vm.container).render(input);
$vm.options = input;
})
.send();
},
renderYtdl(event, $vm) {
helper
.httpClient(helper.generateUrl("/apps/ncdownloader/personal/ytdl/get"))
//.setMethod("GET")
.setHandler((data) => {
if (!data) {
return;
}
let input = [];
for (let key in data) {
if (ytdlOptions.includes(key))
input.push({ name: key, value: data[key], id: key });
}
//settingsForm.getInstance($vm.container).render(input);
$vm.options = input;
})
.send();
},
},
computed: {
optionRows() {
return this.options;
},
},
mounted() {
try {
let data = this.$el.parentElement.getAttribute("data-settings");
data = JSON.parse(data);
let path = "/apps/ncdownloader/personal/save";
this.options = [
{
label: "Downloads Folder ",
id: "ncd_downloader_dir",
value: data.ncd_downloader_dir,
placeholder: data.ncd_downloader_dir ? data.ncd_downloader_dir : "/downloads",
path: path,
},
{
label: "Torrents Folder",
id: "ncd_torrents_dir",
value: data.ncd_torrents_dir,
placeholder: data.ncd_torrents_dir ? data.ncd_torrents_dir : "/torrents",
path: path,
},
];
} catch (e) {
helper.error(e);
}
},
};
</script>

View File

@@ -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 `<div data-tippy-content="${tippy}" class="suggestion-item" data-val="${item}">${item.replace(re, "<b>$1</b>")}</div>`;
}
}).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;

View File

@@ -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 `<div data-tippy-content="${tippy}" class="suggestion-item" data-val="${item}">${item.replace(re, "<b>$1</b>")}</div>`;
}
}).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
}
}