diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index dafef222..e9fae851 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -638,7 +638,13 @@ "bchange35": "Do you give this application permission to send coins?", "bchange36": "Do you want to publish instant to QDN without computing proof-of-work?", "bchange37": "Enter Fullscreen", - "bchange38": "Exit Fullscreen" + "bchange38": "Exit Fullscreen", + "bchange39": "Always allow lists to be retrieved automatically", + "bchange40": "List", + "bchange41": "Do you give this application permission to access this list?", + "bchange42": "Items", + "bchange43": "Do you give this application permission to add to this list?", + "bchange44": "Do you give this application permission to delete from this list?" }, "datapage": { "dchange1": "Data Management", diff --git a/qortal-ui-core/src/components/login-view/login-view.js b/qortal-ui-core/src/components/login-view/login-view.js index 257bbf36..928de419 100644 --- a/qortal-ui-core/src/components/login-view/login-view.js +++ b/qortal-ui-core/src/components/login-view/login-view.js @@ -14,7 +14,7 @@ import './login-section.js' import '../qort-theme-toggle.js' import settings from '../../functional-components/settings-page.js' -import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth } from '../../redux/app/app-actions.js' +import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists } from '../../redux/app/app-actions.js' window.reduxStore = store window.reduxAction = { @@ -22,7 +22,9 @@ window.reduxAction = { removeAutoLoadImageChat: removeAutoLoadImageChat, addChatLastSeen: addChatLastSeen, allowQAPPAutoAuth: allowQAPPAutoAuth, - removeQAPPAutoAuth: removeQAPPAutoAuth + removeQAPPAutoAuth: removeQAPPAutoAuth, + allowQAPPAutoLists: allowQAPPAutoLists, + removeQAPPAutoLists: removeQAPPAutoLists } const animationDuration = 0.7 // Seconds diff --git a/qortal-ui-core/src/components/settings-view/security-view.js b/qortal-ui-core/src/components/settings-view/security-view.js index f9facaea..aaee8212 100644 --- a/qortal-ui-core/src/components/settings-view/security-view.js +++ b/qortal-ui-core/src/components/settings-view/security-view.js @@ -1,7 +1,7 @@ import { LitElement, html, css } from 'lit' import { connect } from 'pwa-helpers' import { store } from '../../store.js' -import { allowQAPPAutoAuth, removeQAPPAutoAuth } from '../../redux/app/app-actions.js' +import { allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists } from '../../redux/app/app-actions.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import '@material/mwc-checkbox' @@ -115,6 +115,12 @@ class SecurityView extends connect(store)(LitElement) { this.checkForAuth(e)} ?checked=${store.getState().app.qAPPAutoAuth}> +
+ + this.checkForLists(e)} ?checked=${store.getState().app.qAPPAutoLists}> +
` } @@ -129,6 +135,13 @@ class SecurityView extends connect(store)(LitElement) { store.dispatch(allowQAPPAutoAuth(true)) } } + checkForLists(e) { + if (e.target.checked) { + store.dispatch(removeQAPPAutoLists(false)) + } else { + store.dispatch(allowQAPPAutoLists(true)) + } + } checkForDownload() { const checkPass = this.shadowRoot.getElementById('downloadBackupPassword').value diff --git a/qortal-ui-core/src/redux/app/actions/app-core.js b/qortal-ui-core/src/redux/app/actions/app-core.js index 43d5d57a..35767dae 100644 --- a/qortal-ui-core/src/redux/app/actions/app-core.js +++ b/qortal-ui-core/src/redux/app/actions/app-core.js @@ -1,5 +1,5 @@ // Core App Actions here... -import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN } from '../app-action-types.js' +import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS } from '../app-action-types.js' export const doUpdateBlockInfo = (blockObj) => { return (dispatch, getState) => { @@ -133,6 +133,19 @@ export const removeQAPPAutoAuth = (payload) => { payload } } +export const allowQAPPAutoLists = (payload) => { + return { + type: ALLOW_QAPP_AUTO_LISTS, + payload + } +} + +export const removeQAPPAutoLists = (payload) => { + return { + type: REMOVE_QAPP_AUTO_LISTS, + payload + } +} export const setChatLastSeen = (payload) => { return { diff --git a/qortal-ui-core/src/redux/app/app-action-types.js b/qortal-ui-core/src/redux/app/app-action-types.js index 4125e6b1..5ace372c 100644 --- a/qortal-ui-core/src/redux/app/app-action-types.js +++ b/qortal-ui-core/src/redux/app/app-action-types.js @@ -24,5 +24,7 @@ export const ADD_AUTO_LOAD_IMAGES_CHAT = 'ADD_AUTO_LOAD_IMAGES_CHAT' export const REMOVE_AUTO_LOAD_IMAGES_CHAT = 'REMOVE_AUTO_LOAD_IMAGES_CHAT' export const ALLOW_QAPP_AUTO_AUTH = 'ALLOW_QAPP_AUTO_AUTH' export const REMOVE_QAPP_AUTO_AUTH = 'REMOVE_QAPP_AUTO_AUTH' +export const ALLOW_QAPP_AUTO_LISTS = 'ALLOW_QAPP_AUTO_LISTS' +export const REMOVE_QAPP_AUTO_LISTS = 'REMOVE_QAPP_AUTO_LISTS' export const SET_CHAT_LAST_SEEN = 'SET_CHAT_LAST_SEEN' export const ADD_CHAT_LAST_SEEN = 'ADD_CHAT_LAST_SEEN' diff --git a/qortal-ui-core/src/redux/app/app-reducer.js b/qortal-ui-core/src/redux/app/app-reducer.js index 6e3849d1..0855ecbf 100644 --- a/qortal-ui-core/src/redux/app/app-reducer.js +++ b/qortal-ui-core/src/redux/app/app-reducer.js @@ -1,6 +1,6 @@ // Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage. import { loadStateFromLocalStorage, saveStateToLocalStorage } from '../../localStorageHelpers.js' -import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN } from './app-action-types.js' +import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS } from './app-action-types.js' import { initWorkersReducer } from './reducers/init-workers.js' import { loginReducer } from './reducers/login-reducer.js' import { setNode, addNode } from './reducers/manage-node.js' @@ -51,6 +51,7 @@ const INITIAL_STATE = { }, autoLoadImageChats: loadStateFromLocalStorage('autoLoadImageChats') || [], qAPPAutoAuth: loadStateFromLocalStorage('qAPPAutoAuth') || false, + qAPPAutoLists: loadStateFromLocalStorage('qAPPAutoLists') || false, chatLastSeen: [] } @@ -192,6 +193,21 @@ export default (state = INITIAL_STATE, action) => { qAPPAutoAuth: action.payload } } + case ALLOW_QAPP_AUTO_LISTS: { + saveStateToLocalStorage("qAPPAutoLists", true) + return { + ...state, + qAPPAutoLists: action.payload + } + } + + case REMOVE_QAPP_AUTO_LISTS: { + saveStateToLocalStorage("qAPPAutoLists", false) + return { + ...state, + qAPPAutoLists: action.payload + } + } case SET_CHAT_LAST_SEEN: { return { diff --git a/qortal-ui-plugins/plugins/core/components/qdn-action-types.js b/qortal-ui-plugins/plugins/core/components/qdn-action-types.js index b51caa6f..1eb816a6 100644 --- a/qortal-ui-plugins/plugins/core/components/qdn-action-types.js +++ b/qortal-ui-plugins/plugins/core/components/qdn-action-types.js @@ -26,4 +26,13 @@ export const GET_WALLET_BALANCE = 'GET_WALLET_BALANCE'; export const SEND_COIN = 'SEND_COIN'; // PUBLISH_MULTIPLE_QDN_RESOURCES -export const PUBLISH_MULTIPLE_QDN_RESOURCES = 'PUBLISH_MULTIPLE_QDN_RESOURCES' \ No newline at end of file +export const PUBLISH_MULTIPLE_QDN_RESOURCES = 'PUBLISH_MULTIPLE_QDN_RESOURCES' + +// GET_LIST_ITEMS +export const GET_LIST_ITEMS = 'GET_LIST_ITEMS' + +// ADD_LIST_ITEMS +export const ADD_LIST_ITEMS = 'ADD_LIST_ITEMS' + +// DELETE_LIST_ITEM +export const DELETE_LIST_ITEM = 'DELETE_LIST_ITEM' \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 11f95266..455cc1ac 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -258,7 +258,7 @@ class WebBrowser extends LitElement { ${this.renderFollowUnfollowButton()}
-
@@ -526,7 +526,8 @@ class WebBrowser extends LitElement { let data = event.data; switch (data.action) { - case actions.GET_USER_ACCOUNT: + case actions.GET_USER_ACCOUNT: { + let skip = false; if (window.parent.reduxStore.getState().app.qAPPAutoAuth) { skip = true; @@ -555,11 +556,199 @@ class WebBrowser extends LitElement { response = JSON.stringify(data); break; } + } + case actions.GET_LIST_ITEMS: { + const requiredFields = ['list_name']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + let skip = false; + if (window.parent.reduxStore.getState().app.qAPPAutoLists) { + skip = true; + } + let res1; + if (!skip) { + res1 = await showModalAndWait( + actions.GET_LIST_ITEMS, + { + list_name: data.list_name + } + ); + }; + + + if (res1 && res1.action === 'accept' || skip) { + + try { + const list = await parentEpml.request('apiCall', { + type: 'api', + url: `/lists/${data.list_name}?apiKey=${this.getApiKey()}`, + }); + response = JSON.stringify(list); + + } catch (error) { + const data = {}; + const errorMsg = "Error in retrieving list" + data['error'] = errorMsg; + response = JSON.stringify(data); + } finally { + break; + } + + } else { + const data = {}; + const errorMsg = "User declined to share list" + data['error'] = errorMsg; + response = JSON.stringify(data); + break; + } + }; + case actions.ADD_LIST_ITEMS: { + const requiredFields = ['list_name', 'items']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + const items = data.items + const list_name = data.list_name + const res = await showModalAndWait( + actions.ADD_LIST_ITEMS, + { + list_name: list_name, + items: items + } + ); + + if (res && res.action === 'accept') { + + try { + const body = { + items: items, + }; + + const bodyToString = JSON.stringify(body); + const data = await parentEpml.request('apiCall', { + type: 'api', + method: 'POST', + url: `/lists/${list_name}?apiKey=${this.getApiKey()}`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json', + }, + }); + response = data + } catch (error) { + const data = {}; + const errorMsg = "Error in adding to list" + data['error'] = errorMsg; + response = JSON.stringify(data); + } finally { + break; + } + + } else { + const data = {}; + const errorMsg = "User declined add to list" + data['error'] = errorMsg; + response = JSON.stringify(data); + break; + } + }; + case actions.DELETE_LIST_ITEM: { + const requiredFields = ['list_name', 'item']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + const item = data.item + const list_name = data.list_name + const res = await showModalAndWait( + actions.DELETE_LIST_ITEM, + { + list_name: list_name, + item: item + } + ); + + if (res && res.action === 'accept') { + + try { + const body = { + items: [item], + }; + + const bodyToString = JSON.stringify(body); + + const data = await parentEpml.request('apiCall', { + type: 'api', + method: 'DELETE', + url: `/lists/${list_name}?apiKey=${this.getApiKey()}`, + body: bodyToString, + headers: { + 'Content-Type': 'application/json', + }, + }); + response = data + } catch (error) { + const data = {}; + const errorMsg = "Error in adding to list" + data['error'] = errorMsg; + response = JSON.stringify(data); + } finally { + break; + } + + } else { + const data = {}; + const errorMsg = "User declined add to list" + data['error'] = errorMsg; + response = JSON.stringify(data); + break; + } + }; + + case actions.LINK_TO_QDN_RESOURCE: case actions.QDN_RESOURCE_DISPLAYED: // Links are handled by the core, but the UI also listens for these actions in order to update the address bar. // Note: don't update this.url here, as we don't want to force reload the iframe each time. - if (this.preview != null && this.preview.length > 0) { this.displayUrl = translate("appspage.schange40"); return; @@ -2501,7 +2690,32 @@ async function showModalAndWait(type, data) { ` : ''} - + ${type === actions.GET_LIST_ITEMS ? ` + + ` : ''} + ${type === actions.ADD_LIST_ITEMS ? ` + + ` : ''} + ${type === actions.DELETE_LIST_ITEM ? ` + + ` : ''} ${type === actions.SEND_CHAT_MESSAGE ? ` ` : ''} @@ -2563,6 +2777,22 @@ async function showModalAndWait(type, data) { window.parent.reduxStore.dispatch(window.parent.reduxAction.allowQAPPAutoAuth(true)) }) } + const labelButton2 = modal.querySelector('#listsButtonLabel'); + if (labelButton2) { + labelButton2.addEventListener('click', () => { + this.shadowRoot.getElementById('listsButton').click(); + }) + } + const checkbox2 = modal.querySelector('#listsButton'); + if (checkbox2) { + checkbox2.addEventListener('click', (e) => { + if (e.target.checked) { + window.parent.reduxStore.dispatch(window.parent.reduxAction.removeQAPPAutoLists(false)) + return + } + window.parent.reduxStore.dispatch(window.parent.reduxAction.allowQAPPAutoLists(true)) + }) + } }); } @@ -2766,6 +2996,8 @@ const styles = ` font-weight: 300; color: var(--black); margin: 0; + word-wrap: break-word; + overflow-wrap: break-word; } .capitalize-first {