codekingpro/portable-devtools
114k
1/////////////////////////////////////////////////////////////2//3// pgAdmin 4 - PostgreSQL Tools4//5// Copyright (C) 2013 - 2024, The pgAdmin Development Team6// This software is released under the PostgreSQL Licence7//8//////////////////////////////////////////////////////////////9 10import _ from 'lodash';11import url_for from 'sources/url_for';12import gettext from 'sources/gettext';13import pgAdmin from 'sources/pgadmin';14import getApiInstance, { callFetch } from '../api_instance';15import usePreferences from '../../../preferences/static/js/store';16 17export const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};18 19 20export const browserTreeState = pgBrowser.browserTreeState = pgBrowser.browserTreeState || {};21 22_.extend(pgBrowser.browserTreeState, {23 24 // Parent node to start saving / reloading the tree state25 parent: 'server',26 27 // The original parent of the browser tree28 orig_parent: 'server_group',29 30 // Stored tree state31 // Sample Object32 // {1:33 // 'paths': [34 // server_group/1,/server/1,/coll-database/1,/database/1,35 // server_group/1,/server/1,/coll-database/1,/database/2,36 // ],37 // 'selected': {38 // 'server/1': 'database/2',39 // 'database/1': 'table/1',40 // },41 // 'conn_status': {42 // 'database/1': 1,43 // 'database/2': 0 ,44 // },45 // }46 // Here key is server ID47 48 stored_state: {},49 50 // Previous tree state51 last_state: {},52 53 is_selected: false,54 55 // Current tree state56 current_state: {},57 58 init: function() {59 60 let saveIntervalId, offExpandFromPrevState, offRemoveFromTreeState, offUpdateTreeState;61 usePreferences.subscribe((prefStore)=>{62 // Subscribe to listen for preferences change63 const save_tree_state_period = prefStore.getPreferences('browser', 'browser_tree_state_save_interval')?.value;64 if (saveIntervalId) {65 clearInterval(saveIntervalId);66 saveIntervalId = null;67 offEventListener();68 }69 if (!_.isUndefined(save_tree_state_period) && save_tree_state_period !== -1) {70 // Save the tree state at given save_tree_state_period71 saveIntervalId = setInterval(this.save_state, (save_tree_state_period) * 1000);72 this.fetch_state.apply(this);73 onEventListener();74 } else if (!_.isUndefined(save_tree_state_period)) {75 offEventListener();76 getApiInstance().delete(url_for('settings.reset_tree_state'))77 .catch(function(error) {78 console.warn(79 gettext('Error resetting the tree saved state."'), error);80 });81 }82 });83 84 const onEventListener = () => {85 // Register event listener86 offExpandFromPrevState = pgBrowser.Events.on('pgadmin:browser:tree:expand-from-previous-tree-state',87 this.expand_from_previous_state.bind(this));88 offRemoveFromTreeState = pgBrowser.Events.on('pgadmin:browser:tree:remove-from-tree-state',89 this.remove_from_cache.bind(this));90 offUpdateTreeState = pgBrowser.Events.on('pgadmin:browser:tree:update-tree-state',91 this.update_cache.bind(this));92 };93 94 const offEventListener = () => {95 // Deregister event listener96 offExpandFromPrevState?.();97 offRemoveFromTreeState?.();98 offUpdateTreeState?.();99 };100 101 },102 save_state: function() {103 104 let self = pgBrowser.browserTreeState;105 if(self.last_state == JSON.stringify(self.current_state))106 return;107 108 /* Using fetch with keepalive as the browser may109 cancel the axios request on tab close. keepalive will110 make sure the request is completed */111 callFetch(112 url_for('settings.save_tree_state'), {113 keepalive: true,114 method: 'POST',115 body: JSON.stringify(self.current_state)116 })117 .then(()=> {118 self.last_state = JSON.stringify(self.current_state);119 self.fetch_state();120 })121 .catch((error)=> {122 console.warn(123 gettext('Error resetting the tree saved state."'), error);124 });125 },126 fetch_state: function() {127 128 let self = this;129 130 getApiInstance().get(131 url_for('settings.get_tree_state'),132 ).then((res)=> {133 self.stored_state = res.data;134 }).catch(function(error) {135 console.warn(136 gettext('Error resetting the tree saved state."'), error);137 });138 },139 update_cache: function(item) {140 let data = item && pgBrowser.tree.itemData(item),141 treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),142 topParent,143 pathIDs = pgBrowser.tree.pathId(pgBrowser.tree.parent(item)),144 oldPath = pathIDs.join(),145 path = [],146 tmpIndex = -1;147 148 // If no parent or the server not in tree hierarchy then return149 if (!pgBrowser.tree.hasParent(item) || !(this.parent in treeHierarchy) || (data._type === 'server' && !data.connected))150 return;151 152 topParent = treeHierarchy[this.parent]['_id'];153 154 155 if (pgBrowser.tree.isOpen(item)) {156 // Store paths157 158 pathIDs.push(data.id);159 path = pathIDs.join();160 161 if (!(topParent in this.current_state)) {162 this.current_state[topParent] = {'paths': [], 'selected': {}, 'conn_status': {}, 'is_opened': {}};163 }164 165 // IF the current path is already saved then return166 let index = _.find(this.current_state[topParent]['paths'], function(tData) {167 return (tData.search(path) !== -1);168 });169 if(_.isUndefined(index)) {170 171 // Add / Update the current item into the tree path172 if (!_.isUndefined(this.current_state[topParent]['paths'])) {173 tmpIndex = this.current_state[topParent]['paths'].indexOf(oldPath);174 } else {175 this.current_state[topParent]['paths'] = [];176 }177 if (tmpIndex !== -1) {178 this.current_state[topParent]['paths'][tmpIndex] = path;179 }180 else {181 this.current_state[topParent]['paths'].push(path);182 }183 }184 185 }186 187 // Store current selected item and database connection status188 this.update_database_status(item);189 this.update_current_selected_item(treeHierarchy);190 191 },192 remove_from_cache: function(item) {193 let self= this,194 treeData = self.stored_state || {},195 data = item && pgBrowser.tree.itemData(item),196 treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);197 198 if (treeHierarchy === null || !pgBrowser.tree.hasParent(item) || !(treeHierarchy.hasOwnProperty(self.parent)))199 return;200 201 let topParent = treeHierarchy?.[self.parent]['_id'],202 origParent = treeHierarchy?.[self.orig_parent]['id'];203 204 this.update_database_status(item);205 206 if (data._type == self.parent || data._type == 'database') {207 if (treeData?.[topParent]?.['paths'] && self.current_state?.[topParent]?.['paths']) {208 treeData[topParent]['paths'] = self.current_state[topParent]['paths'];209 self.save_state();210 }211 return;212 }213 214 if (pgBrowser.tree.isClosed(item)) {215 let tmpTreeData = self.current_state[topParent]['paths'],216 databaseId;217 218 if (treeHierarchy.hasOwnProperty('database'))219 databaseId = treeHierarchy['database']['id'];220 221 if (!_.isUndefined(tmpTreeData) && !_.isUndefined(tmpTreeData.length)) {222 let tcnt = 0, tmpItemDataStr;223 _.each(tmpTreeData, function(tData) {224 if (_.isUndefined(tData))225 return;226 227 let tmpItemData = tData.split(',');228 229 if (tmpItemData.indexOf(data.id) !== -1 ) {230 if (databaseId === undefined || (databaseId !== undefined && tmpItemData.indexOf(databaseId) !== -1)) {231 232 let index = tmpItemData.indexOf(data.id);233 tmpItemData.splice(index);234 tmpItemDataStr = tmpItemData.join();235 236 if (tmpItemDataStr == origParent)237 self.current_state[topParent]['paths'].splice(tData, 1);238 else239 self.current_state[topParent]['paths'][tcnt] = tmpItemDataStr;240 }241 }242 tcnt ++;243 });244 }245 }246 },247 expand_from_previous_state: function(item) {248 let self = this,249 treeData = this.stored_state || {},250 data = item && pgBrowser.tree.itemData(item),251 treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);252 253 254 if (treeHierarchy === null || !pgBrowser.tree.hasParent(item) || !(treeHierarchy.hasOwnProperty(self.parent)))255 return;256 257 // If the server node is open then only we should populate the tree258 if (data['_type'] == self.parent && (pgBrowser.tree.isOpen(item) === false))259 return;260 261 let tmpTreeData = treeData[treeHierarchy[self.parent]['_id']];262 263 264 // If the server node is open then only we should populate the tree265 if (data['_type'] == 'database' && tmpTreeData && 'conn_status' in tmpTreeData && 'is_opened' in tmpTreeData &&266 (tmpTreeData['conn_status'][data['id']] === 0 || tmpTreeData['is_opened'][data['id']] === 0 ||267 !(data['id'] in tmpTreeData['is_opened'])))268 return;269 270 271 if (!_.isUndefined(tmpTreeData) && ('paths' in tmpTreeData) && !_.isUndefined(tmpTreeData['paths'].length)) {272 let tmpTreeDataPaths = [...tmpTreeData['paths']],273 databaseId;274 275 if (treeHierarchy.hasOwnProperty('database'))276 databaseId = treeHierarchy['database']['id'];277 278 _.each(tmpTreeDataPaths, function(tData) {279 if (_.isUndefined(tData))280 return;281 282 let tmpItemData = tData.split(',');283 284 // If the item is in the lastTreeState then open it285 if (tmpItemData.indexOf(data.id) !== -1) {286 if (databaseId === undefined || (databaseId !== undefined && tmpItemData.indexOf(databaseId) !== -1)) {287 288 let index = tmpItemData.indexOf(data.id);289 290 pgBrowser.tree.open(item);291 pgBrowser.tree.ensureLoaded(item);292 if (index == (tmpItemData.length - 1 )) {293 let tIndex = treeData[treeHierarchy[self.parent]['_id']]['paths'].indexOf(tData);294 treeData[treeHierarchy[self.parent]['_id']]['paths'].splice(tIndex, 1);295 }296 }297 }298 });299 }300 301 this.select_tree_item(item);302 303 },304 update_database_status: function(item) {305 let data = item && pgBrowser.tree.itemData(item),306 treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);307 308 if (treeHierarchy.hasOwnProperty('database')) {309 let databaseItem = treeHierarchy['database']['id'],310 topParent = treeHierarchy?.[this.parent]['_id'];311 312 if (topParent in this.current_state && 'selected' in this.current_state[topParent]) {313 if (treeHierarchy['database'].connected) {314 this.current_state[topParent]['conn_status'][databaseItem] = 1;315 }316 else {317 this.current_state[topParent]['conn_status'][databaseItem] = 0;318 }319 320 if(data._type == 'database') {321 if (pgBrowser.tree.isOpen(item)) {322 this.current_state[topParent]['is_opened'][databaseItem] = 1;323 }324 else {325 this.current_state[topParent]['is_opened'][databaseItem] = 0;326 }327 }328 }329 }330 },331 update_current_selected_item(treeHierarchy) {332 if (!(this.parent in treeHierarchy))333 return;334 335 let topParent = treeHierarchy?.[this.parent]['_id'],336 selectedItem = pgBrowser.tree.itemData(pgBrowser.tree.selected()),337 databaseItem;338 339 selectedItem = selectedItem ? selectedItem.id : undefined;340 341 if (treeHierarchy.hasOwnProperty('database')) {342 databaseItem = treeHierarchy['database']['id'];343 }344 345 if (topParent in this.current_state && 'selected' in this.current_state[topParent]346 && !_.isUndefined(selectedItem)) {347 this.current_state[topParent]['selected'][treeHierarchy[this.parent]['id']] = selectedItem;348 349 if (!_.isUndefined(databaseItem))350 this.current_state[topParent]['selected'][databaseItem] = selectedItem;351 }352 },353 select_tree_item(item) {354 let treeData = this.stored_state || {},355 data = item && pgBrowser.tree.itemData(item),356 treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),357 tmpTreeData = treeData[treeHierarchy[this.parent]['_id']];358 359 if (treeHierarchy.hasOwnProperty('server')) {360 let selectedItem = treeHierarchy['server']['id'];361 362 if (tmpTreeData && 'selected' in tmpTreeData && selectedItem in tmpTreeData['selected']) {363 if (tmpTreeData['selected'][selectedItem] == data.id) {364 this.is_selected = true;365 pgBrowser.tree.select(item, true, 'center');366 }367 }368 }369 },370});371 