1/** 2 * Tags plugin. 3 * 4 * - Set tags via dialog 5 * - Toggle hidden tags 6 * - Stateless filter 7 * 8 * TODO: 9 * 10 * - Add hiddenTags to viewState of page 11 * - Export to PDF ignores current tags 12 * - Sync hiddenTags with removed tags 13 */ 14Draw.loadPlugin(function(editorUi) 15{ 16 var div = document.createElement('div'); 17 18 // Adds resource for action 19 mxResources.parse('hiddenTags=Hidden Tags'); 20 21 // Adds action 22 editorUi.actions.addAction('hiddenTags...', function() 23 { 24 if (editorUi.hiddenTagsWindow == null) 25 { 26 editorUi.hiddenTagsWindow = new HiddenTagsWindow(editorUi, document.body.offsetWidth - 380, 120, 300, 240); 27 editorUi.hiddenTagsWindow.window.addListener('show', function() 28 { 29 editorUi.fireEvent(new mxEventObject('hiddenTags')); 30 }); 31 editorUi.hiddenTagsWindow.window.addListener('hide', function() 32 { 33 editorUi.fireEvent(new mxEventObject('hiddenTags')); 34 }); 35 editorUi.hiddenTagsWindow.window.setVisible(true); 36 editorUi.fireEvent(new mxEventObject('hiddenTags')); 37 } 38 else 39 { 40 editorUi.hiddenTagsWindow.window.setVisible(!editorUi.hiddenTagsWindow.window.isVisible()); 41 } 42 }); 43 44 var menu = editorUi.menus.get('extras'); 45 var oldFunct = menu.funct; 46 47 menu.funct = function(menu, parent) 48 { 49 oldFunct.apply(this, arguments); 50 51 editorUi.menus.addMenuItems(menu, ['-', 'hiddenTags'], parent); 52 }; 53 54 var HiddenTagsWindow = function(editorUi, x, y, w, h) 55 { 56 var graph = editorUi.editor.graph; 57 58 var div = document.createElement('div'); 59 div.style.overflow = 'hidden'; 60 div.style.padding = '12px 8px 12px 8px'; 61 div.style.height = 'auto'; 62 63 var searchInput = document.createElement('input'); 64 searchInput.setAttribute('placeholder', 'Type in the tags and press Enter to add them'); 65 searchInput.setAttribute('type', 'text'); 66 searchInput.style.width = '100%'; 67 searchInput.style.boxSizing = 'border-box'; 68 searchInput.style.fontSize = '12px'; 69 searchInput.style.borderRadius = '4px'; 70 searchInput.style.padding = '4px'; 71 searchInput.style.marginBottom = '8px'; 72 div.appendChild(searchInput); 73 74 var filterInput = searchInput.cloneNode(true); 75 filterInput.setAttribute('placeholder', 'Filter tags'); 76 div.appendChild(filterInput); 77 78 var tagCloud = document.createElement('div'); 79 tagCloud.style.position = 'relative'; 80 tagCloud.style.fontSize = '12px'; 81 tagCloud.style.height = 'auto'; 82 div.appendChild(tagCloud); 83 84 var graph = editorUi.editor.graph; 85 var lastValue = null; 86 87 function getLookup(tagList) 88 { 89 var lookup = {}; 90 91 for (var i = 0; i < tagList.length; i++) 92 { 93 lookup[tagList[i].toLowerCase()] = true; 94 } 95 96 return lookup; 97 }; 98 99 function getAllTags() 100 { 101 return graph.getTagsForCells(graph.model.getDescendants( 102 graph.model.getRoot())); 103 }; 104 105 /** 106 * Returns true if tags exist and are all in lookup. 107 */ 108 function matchTags(tags, lookup, tagCount) 109 { 110 if (tags.length > 0) 111 { 112 var tmp = tags.toLowerCase().split(' '); 113 114 if (tmp.length > tagCount) 115 { 116 return false; 117 } 118 else 119 { 120 for (var i = 0; i < tmp.length; i++) 121 { 122 if (lookup[tmp[i]] == null) 123 { 124 return false; 125 } 126 } 127 128 return true; 129 } 130 } 131 else 132 { 133 return false; 134 } 135 }; 136 137 var hiddenTags = {}; 138 var hiddenTagCount = 0; 139 var graphIsCellVisible = graph.isCellVisible; 140 141 graph.isCellVisible = function(cell) 142 { 143 return graphIsCellVisible.apply(this, arguments) && 144 (hiddenTagCount == 0 || 145 !matchTags(graph.getTagsForCell(cell), hiddenTags, hiddenTagCount)); 146 }; 147 148 function setCellsVisibleForTag(tag, visible) 149 { 150 var cells = graph.getCellsForTags([tag], null, true); 151 152 // Ignores layers for selection 153 var temp = []; 154 155 for (var i = 0; i < cells.length; i++) 156 { 157 if (graph.model.isVertex(cells[i]) || graph.model.isEdge(cells[i])) 158 { 159 temp.push(cells[i]); 160 } 161 } 162 163 graph.setCellsVisible(cells, visible); 164 }; 165 166 function updateSelectedTags(tags, selected, selectedColor, filter) 167 { 168 tagCloud.innerHTML = ''; 169 170 var title = document.createElement('div'); 171 title.style.marginBottom = '8px'; 172 mxUtils.write(title, (filter != null) ? 'Select hidden tags:' : 'Or add/remove existing tags for cell(s):'); 173 tagCloud.appendChild(title); 174 175 var found = 0; 176 177 for (var i = 0; i < tags.length; i++) 178 { 179 if (filter == null || tags[i].substring(0, filter.length) == filter) 180 { 181 var span = document.createElement('span'); 182 span.style.display = 'inline-block'; 183 span.style.padding = '6px 8px'; 184 span.style.borderRadius = '6px'; 185 span.style.marginBottom = '8px'; 186 span.style.maxWidth = '80px'; 187 span.style.overflow = 'hidden'; 188 span.style.textOverflow = 'ellipsis'; 189 span.style.cursor = 'pointer'; 190 span.setAttribute('title', tags[i]); 191 span.style.border = '1px solid #808080'; 192 mxUtils.write(span, tags[i]); 193 194 if (selected[tags[i]]) 195 { 196 span.style.background = selectedColor; 197 span.style.color = '#ffffff'; 198 } 199 else 200 { 201 span.style.background = (Editor.isDarkMode()) ? 'transparent' : '#ffffff'; 202 } 203 204 mxEvent.addListener(span, 'click', (function(tag) 205 { 206 return function() 207 { 208 if (!selected[tag]) 209 { 210 if (!graph.isSelectionEmpty()) 211 { 212 graph.addTagsForCells(graph.getSelectionCells(), [tag]) 213 } 214 else 215 { 216 hiddenTags[tag] = true; 217 hiddenTagCount++; 218 refreshUi(); 219 220 window.setTimeout(function() 221 { 222 graph.refresh(); 223 }, 0); 224 } 225 } 226 else 227 { 228 if (!graph.isSelectionEmpty()) 229 { 230 graph.removeTagsForCells(graph.getSelectionCells(), [tag]) 231 } 232 else 233 { 234 delete hiddenTags[tag]; 235 hiddenTagCount--; 236 refreshUi(); 237 238 window.setTimeout(function() 239 { 240 graph.refresh(); 241 }, 0); 242 } 243 } 244 }; 245 })(tags[i])); 246 247 tagCloud.appendChild(span); 248 mxUtils.write(tagCloud, ' '); 249 found++; 250 } 251 } 252 253 if (found == 0) 254 { 255 mxUtils.write(tagCloud, 'No tags found'); 256 } 257 }; 258 259 function updateTagCloud(tags) 260 { 261 updateSelectedTags(tags, hiddenTags, '#bb0000', filterInput.value); 262 }; 263 264 function refreshUi() 265 { 266 if (graph.isSelectionEmpty()) 267 { 268 updateTagCloud(getAllTags(), hiddenTags); 269 searchInput.style.display = 'none'; 270 filterInput.style.display = ''; 271 } 272 else 273 { 274 updateSelectedTags(getAllTags(), getLookup(graph.getCommonTagsForCells(graph.getSelectionCells())), '#2873e1'); 275 searchInput.style.display = ''; 276 filterInput.style.display = 'none'; 277 } 278 } 279 280 refreshUi(); 281 282 graph.selectionModel.addListener(mxEvent.CHANGE, function(sender, evt) 283 { 284 refreshUi(); 285 }); 286 287 graph.model.addListener(mxEvent.CHANGE, function(sender, evt) 288 { 289 refreshUi(); 290 }); 291 292 mxEvent.addListener(filterInput, 'keyup', function() 293 { 294 updateTagCloud(getAllTags()); 295 }); 296 297 mxEvent.addListener(searchInput, 'keyup', function(evt) 298 { 299 // Ctrl or Cmd keys 300 if (evt.keyCode == 13) 301 { 302 graph.addTagsForCells(graph.getSelectionCells(), searchInput.value.toLowerCase().split(' ')); 303 searchInput.value = ''; 304 } 305 }); 306 307 this.window = new mxWindow(mxResources.get('hiddenTags'), div, x, y, w, null, true, true); 308 this.window.destroyOnClose = false; 309 this.window.setMaximizable(false); 310 this.window.setResizable(true); 311 this.window.setScrollable(true); 312 this.window.setClosable(true); 313 this.window.contentWrapper.style.overflowY = 'scroll'; 314 315 this.window.addListener('show', mxUtils.bind(this, function() 316 { 317 this.window.fit(); 318 319 if (this.window.isVisible()) 320 { 321 searchInput.focus(); 322 323 if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5) 324 { 325 searchInput.select(); 326 } 327 else 328 { 329 document.execCommand('selectAll', false, null); 330 } 331 } 332 else 333 { 334 graph.container.focus(); 335 } 336 })); 337 338 this.window.setLocation = function(x, y) 339 { 340 var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth; 341 var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight; 342 343 x = Math.max(0, Math.min(x, iw - this.table.clientWidth)); 344 y = Math.max(0, Math.min(y, ih - this.table.clientHeight - 48)); 345 346 if (this.getX() != x || this.getY() != y) 347 { 348 mxWindow.prototype.setLocation.apply(this, arguments); 349 } 350 }; 351 352 var resizeListener = mxUtils.bind(this, function() 353 { 354 var x = this.window.getX(); 355 var y = this.window.getY(); 356 357 this.window.setLocation(x, y); 358 }); 359 360 mxEvent.addListener(window, 'resize', resizeListener); 361 362 this.destroy = function() 363 { 364 mxEvent.removeListener(window, 'resize', resizeListener); 365 this.window.destroy(); 366 } 367 }; 368 369}); 370