Last active
July 12, 2023 14:23
-
-
Save ThomasRohde/92ab43ec8982c863bd2a85ae3907c701 to your computer and use it in GitHub Desktop.
Bulk property editor for #Archi #JArchi #ArchiMateTool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Author: Thomas Klok Rohde | |
Description: Edit properties of selected elements in a spreadsheet | |
History: | |
October 22, 2022 : Created | |
*/ | |
console.show(); | |
console.clear(); | |
let objects = []; | |
//$(selection).each(o => { if ($(o).is("element")) objects.push(o); } ); | |
$(selection).each(o => objects.push(o)); | |
if (objects.length == 0) throw new Error("No elements selected"); | |
// Super-set of all the properties defined for the selected objects | |
let properties = new Set(); | |
objects.forEach(o => { | |
let props = o.prop(); | |
props.forEach(p => properties.add(p)); | |
}); | |
let columns = new Array({ | |
type: 'text', | |
width: '250', | |
name: 'name', | |
title: 'Name', | |
align: 'left', | |
readOnly: true, | |
}); | |
let mapping = new Map(); | |
let reverse = new Map(); | |
let jstypes = new Set(["hidden", "text", "checkbox", "calendar", "dropdown", "color", "image", "html"]); | |
properties.forEach(p => { | |
let cmps = p.split(":"); | |
let jstype = 'text'; | |
let jsvalues; | |
let jsname = cmps[1]; | |
if (jstypes.has(jsname)) { | |
if (cmps.length == 2) { | |
// Syntax: <name> | <name> ":" <JSON object> | |
let parse = cmps[1].split(/\s*(\w+)\s*(\[[^\]]*\])/); | |
if (parse.length > 0) { | |
if (parse.length == 1) | |
jstype = parse[0]; | |
else | |
jstype = parse[1]; | |
if (parse.length == 4) { | |
try { | |
jsvalues = JSON.parse(parse[2]); | |
} | |
catch (error) { | |
console.log("Not a JSON value: ", parse[2]); | |
} | |
} | |
} | |
else jsname = cmps[1]; | |
} | |
} else jsname = p; | |
mapping.set(jsname, p); | |
reverse.set(p, jsname); | |
let column = { | |
type: jstype, | |
width: '100', | |
name: jsname, | |
align: 'left', | |
title: jsname | |
}; | |
if (jsvalues && jstype == 'dropdown') column.source = jsvalues; | |
if (jstype == 'color') column.render = 'square'; | |
if (jstype == 'rating') column.render = 'stars'; | |
if (jstype == 'calendar') column.options = { format: 'DD/MM/YYYY' }; | |
columns.push(column); | |
}) | |
// We need the id's to properly get the data back to the elements - but we don't need to show them | |
columns.push({ | |
type: 'hidden', | |
name: 'id', | |
align: 'left', | |
title: 'id' | |
}); | |
let data = []; | |
objects.forEach(o => { | |
let row = { | |
name: ($(o).is("relationship") ? `${o.type} (${o.source.name} - ${o.target.name})` : o.name), | |
}; | |
properties.forEach(p => { | |
let value = o.prop(p); | |
row[reverse.get(p)] = (value) ? o.prop(p) : ""; | |
}) | |
row.id = o.id; | |
data.push(row); | |
}) | |
const SWT = Java.type('org.eclipse.swt.SWT'); | |
const FillLayout = Java.type('org.eclipse.swt.layout.FillLayout'); | |
const Shell = Java.type('org.eclipse.swt.widgets.Shell'); | |
const Browser = Java.type('org.eclipse.swt.browser.Browser'); | |
const ProgressAdapter = Java.extend(Java.type('org.eclipse.swt.browser.ProgressAdapter')); | |
const LocationAdapter = Java.extend(Java.type('org.eclipse.swt.browser.LocationAdapter')); | |
const CustomFunction = Java.extend(Java.type('org.eclipse.swt.browser.BrowserFunction')); | |
const IArchiImages = Java.type('com.archimatetool.editor.ui.IArchiImages'); | |
const ImageFactory = Java.type('com.archimatetool.editor.ui.ImageFactory'); | |
let display = shell.getDisplay(); | |
let newShell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP | SWT.APPLICATION_MODAL); | |
newShell.setText("Edit properties"); | |
newShell.setLayout(new FillLayout()); | |
let sheet = []; | |
let headers = []; | |
let jsondata = {}; | |
html = `<html> | |
<script src="${__DIR__ + "/lib/jexcel.js"}"></script> | |
<script src="${__DIR__ + "/lib/jsuites.js"}"></script> | |
<link rel="stylesheet" href="https://bossanova.uk/jspreadsheet/v4/jexcel.css" type="text/css" /> | |
<link rel="stylesheet" href="https://jsuites.net/v4/jsuites.css" type="text/css" /> | |
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" /> | |
<style> | |
:root { | |
font-family: Sans-serif; | |
} | |
button { | |
margin-right: 10px; | |
padding: 5px; | |
width: 100px; | |
} | |
.jexcel > tbody > tr > td.readonly { | |
color: rgba(0,0,0,0.6); | |
} | |
table | |
{ | |
font-size:0.8em; | |
} | |
</style> | |
<div id="buttons"> | |
<button onclick="okPressed()">Ok</button><button onclick="cancelPressed()">Cancel</button> | |
<p style="font-size: 0.7em;">Double-click in a cell to edit. Right-click in the table header to add or rename properties (columns) or save to Excel. Empty cells will delete properties. | |
Keyboard shortcuts work as expected, including copy/paste and undo. You can add a calculated column and enter an 'Excel' formula, e.g., '=B2*1.1'. The calculated values will be returned and the formulas lost. | |
</p> | |
</div> | |
<div id="spreadsheet"></div> | |
<script> | |
let data = ${JSON.stringify(data, null, 3)}; | |
let columns = ${JSON.stringify(columns, null, 3)}; | |
function okPressed() { | |
okPressedEvent( | |
JSON.stringify(table.getData(false,false)), | |
JSON.stringify(table.getHeaders(true)), | |
JSON.stringify(table.getJson())); | |
} | |
function cancelPressed() { | |
cancelPressedEvent(); | |
} | |
let table = jspreadsheet(document.getElementById('spreadsheet'), { | |
data: data, | |
columns: columns, | |
allowDeleteColumn: false, | |
includeHeadersOnDownload: true, | |
freezeColumns: 1, | |
allowInsertRow: false, | |
allowManualInsertRow: false, | |
allowDeleteRow: false, | |
allowDeletingAllRows: false, | |
copyCompatibility: true | |
}); | |
</script> | |
</html>`; | |
var okPressed = false; | |
var cancelPressed = false; | |
let browser = new Browser(newShell, SWT.NONE); | |
browser.addProgressListener(new ProgressAdapter({ | |
completed: function (event) { | |
let fncOk = new CustomFunction(browser, "okPressedEvent", { | |
function: (args) => { | |
okPressed = true; | |
sheet = JSON.parse(args[0]); | |
headers = JSON.parse(args[1]); | |
jsondata = JSON.parse(args[2]); | |
} | |
}); | |
let fncCancel = new CustomFunction(browser, "cancelPressedEvent", { | |
function: (args) => { | |
cancelPressed = true; | |
} | |
}); | |
browser.addLocationListener(new LocationAdapter({ | |
changed: (e) => { | |
browser.removeLocationListener(this); | |
fncOk.dispose(); | |
fncCancel.dispose(); | |
} | |
})); | |
} | |
})); | |
// Write the HTML to a temporary file, so we are allowed to execute a local script | |
let System = Java.type('java.lang.System'); | |
let tmpfile = System.getProperty("java.io.tmpdir") + "editproperties.html"; | |
$.fs.writeFile(tmpfile, html); | |
browser.setUrl("file:///" + tmpfile); | |
// Set icon to Archi icon | |
newShell.setImage(IArchiImages.ImageFactory.getImage(IArchiImages.ICON_APP)); | |
// Some sizing heuristics. I have no clue what I'm doing! | |
// newShell.setSize(((columns.length * 100) + 250 + 50) * 1.25, (objects.length > 30 ? 30 : objects.length) * 41 * 1.25); | |
newShell.open(); | |
while (!newShell.isDisposed() && !okPressed && !cancelPressed) { | |
if (!display.readAndDispatch()) display.sleep(); | |
} | |
if (okPressed) { | |
jsondata.forEach(row => { | |
let concept = $('#' + row.id); | |
let props = new Set(); | |
concept.prop().forEach(p => props.add(p)); | |
for (const key in row) { | |
// Skip if columns are id or name - we shouldn't mess with them | |
if (key == "id" || key == "name") continue; | |
// New columns will have the column number as key in the jsondata | |
let columnId = parseInt(key); | |
let realKey = key; | |
if (!isNaN(columnId)) | |
// Praying that headers contain the column names in the right order! | |
realKey = headers[columnId] ? headers[columnId] : realKey; | |
// The table name have typing removed for readability. Fetch the original property names | |
let originalKey = mapping.get(realKey); | |
if (!originalKey) originalKey = realKey; | |
if (row[key]) | |
concept.prop(originalKey, row[key].toString()); | |
else | |
concept.removeProp(originalKey); | |
// Make sure all keys are marked as visited | |
props.delete(key); | |
props.delete(realKey); | |
props.delete(originalKey); | |
} | |
// Remove properties from renamed columns | |
props.forEach(p => { | |
concept.removeProp(p); | |
}) | |
}) | |
} | |
else if (cancelPressed) | |
console.log('Cancelled.') | |
newShell.dispose(); |
@trischbeck Sorry for the delay - I don't check alerts often. I have commented out the setSize() code, and I got it to work again here. Please remember to select a elements with some properties.
Dear @ThomasRohde - Thanks for your response. I tried many thinks now. Commented out the setSize() and also placed jsuites.js and excel.js in the lib directory. But yet I only get the welcome screen and nothing else.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Thomas, nothing happens when I run this script. All I see is a window with instructions and two buttons "OK" and "cancel". Any ideas? Kind regards, Thomas.