Skip to content

Commit f6bd25c

Browse files
committed
- fix issues with pending appends not being escaped before handing over to javascript
- enhance file chooser - enhance logging - fix remove not removing from pending appends
1 parent 3a80353 commit f6bd25c

File tree

10 files changed

+260
-85
lines changed

10 files changed

+260
-85
lines changed

src/main/java/com/osiris/desku/App.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ public class App {
7474
public static File styles;
7575
public static File javascript;
7676

77+
/**
78+
* Make sure {@link LoggerParams} has debugging enabled for this to work. <br>
79+
* If this is enabled the debug output will include a much more detailed output
80+
* related to the html that is added, the attributes being set etc. <br>
81+
* This also adds similar logging to the browsers console output. <br>
82+
*/
83+
public static boolean isInDepthDebugging = false;
84+
7785
static {
7886
updateDirs();
7987
}
@@ -116,7 +124,7 @@ public static void updateDirs(){
116124

117125
public static class LoggerParams{
118126
public String name = "Logger";
119-
public boolean debug = true;
127+
public boolean debug = false;
120128
public File logsDir;
121129
public File latestLogFile;
122130
public File mirrorOutFile;
@@ -160,12 +168,12 @@ public static void init(UIManager uiManager, LoggerParams loggerParams) {
160168
AL.start(loggerParams.name, loggerParams.debug, loggerParams.latestLogFile, loggerParams.ansi, loggerParams.forceAnsi);
161169
AL.mirrorSystemStreams(loggerParams.mirrorOutFile, loggerParams.mirrorErrFile);
162170
}
163-
AL.info("Starting application...");
164-
AL.info("workingDir = " + workingDir);
165-
AL.info("tempDir = " + tempDir);
166-
AL.info("userDir = " + userDir);
167-
AL.info("htmlDir = " + htmlDir);
168-
AL.info("Java = " + System.getProperty("java.vendor") + " " + System.getProperty("java.version"));
171+
AL.debug(App.class, "Starting application...");
172+
AL.debug(App.class, "workingDir = " + workingDir);
173+
AL.debug(App.class, "tempDir = " + tempDir);
174+
AL.debug(App.class, "userDir = " + userDir);
175+
AL.debug(App.class, "htmlDir = " + htmlDir);
176+
AL.debug(App.class, "Java = " + System.getProperty("java.vendor") + " " + System.getProperty("java.version"));
169177

170178
// Clear the directory at each app startup, since
171179
// its aim is to provide a cache to load pages faster
@@ -187,7 +195,7 @@ public static void init(UIManager uiManager, LoggerParams loggerParams) {
187195
appendToGlobalCSS(getCSS(Bootstrap.class));
188196
appendToGlobalJS(getJS(Bootstrap.class));
189197

190-
AL.info("Started application successfully!");
198+
AL.debug(App.class, "Started application successfully!");
191199
} catch (Exception e) {
192200
throw new RuntimeException(e);
193201
}

src/main/java/com/osiris/desku/ui/Component.java

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import java.lang.reflect.InvocationTargetException;
2424
import java.lang.reflect.Modifier;
25+
import java.util.ArrayList;
26+
import java.util.List;
2527
import java.util.Objects;
2628
import java.util.concurrent.ConcurrentHashMap;
2729
import java.util.concurrent.CopyOnWriteArrayList;
@@ -61,6 +63,11 @@ public class Component<THIS extends Component<THIS, VALUE>, VALUE> {
6163
* </pre>
6264
*/
6365
public final int id = idCounter.getAndIncrement();
66+
/**
67+
* List of children. Normally it's read-only. <br>
68+
* Thus do not modfify directly and use methods like {@link #add(Component[])} or {@link #remove(Component[])}
69+
* instead, to ensure the changes are also visible in the browser.
70+
*/
6471
public final CopyOnWriteArrayList<Component> children = new CopyOnWriteArrayList<>();
6572
/**
6673
* Executed when a child was added on the Java side. <br>
@@ -149,31 +156,30 @@ public boolean isAttached(){
149156
child.element.remove();
150157
child.update();
151158

152-
// Update UI
153-
if (isAttached && !ui.isLoading()){
154-
ui.executeJavaScriptSafely(ui.jsGetComp("comp", id) +
155-
ui.jsGetComp("childComp", child.id) +
156-
"comp.removeChild(childComp);\n",
157-
"internal", 0);
158-
}
159-
160-
child.isAttached = false;
161-
onChildRemove.execute(child);
162159
}
160+
UI.PendingAppend.removeFromPendingAppends(ui, child);
161+
162+
// Update UI always
163+
// Child might have already been removed in Java but not in JS
164+
executeJS(ui.jsGetComp("childComp", child.id) +
165+
"comp.removeChild(childComp);\n" +
166+
(App.isInDepthDebugging ? "console.log('parent comp:', comp); console.log('➡️❌ removed childComp:', childComp); \n" : ""));
167+
168+
child.isAttached = false;
169+
onChildRemove.execute(child);
163170
};
164171
public Consumer<Component> _removeSelf = self -> {
165172
UI ui = UI.get(); // Necessary for updating the actual UI via JavaScript
166173
if (self.element.parent() != null){
167174
self.element.remove();
168175
}
169176
self.update();
177+
UI.PendingAppend.removeFromPendingAppends(ui, self);
170178

171179
// Update UI
172-
if (isAttached && !ui.isLoading()){
173-
ui.executeJavaScriptSafely(ui.jsGetComp("comp", self.id) +
174-
"comp.parentNode.removeChild(comp);\n",
175-
"internal", 0);
176-
}
180+
executeJS(ui.jsGetComp("comp", self.id) +
181+
"comp.parentNode.removeChild(comp);\n"+
182+
(App.isInDepthDebugging ? "console.log('parent comp:', comp.parentNode); console.log('➡️❌ removed self:', comp); \n" : ""));
177183

178184
self.isAttached = false;
179185
//onChildRemove.execute(self);
@@ -191,6 +197,8 @@ public boolean isAttached(){
191197
e.childComp.update();
192198
element.insertChildren(iOtherComp, e.childComp.element);
193199
} else if (e.isReplace) {
200+
// childComp is the new component to be added
201+
// and otherChildComp is the one that gets removed/replaced
194202
int iOtherComp = children.indexOf(e.otherChildComp);
195203
children.set(iOtherComp, e.childComp);
196204
e.childComp.update();
@@ -263,7 +271,9 @@ public boolean isAttached(){
263271
executeJS("comp.setAttribute(`" + key
264272
+ "`, `" + value + "`);\n" +
265273
"comp[`"+key+"`] = `"+value+"`\n"); // Change UI representation
266-
//System.out.println(key+" = "+ value);
274+
if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" _attributeChange javascript -> "+key+" = "+ value);
275+
} else {
276+
if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" _attributeChange Java -> "+key+" = "+ value);
267277
}
268278

269279
} else {// Remove attribute
@@ -334,11 +344,15 @@ public Component(@UnknownNullability VALUE value, @NotNull Class<VALUE> valueCla
334344
* that was set in the constructor.
335345
*/
336346
public THIS getValue(Consumer<@NotNull VALUE> v) {
347+
337348
UI ui = UI.get();
338-
if(!isAttached || ui == null || ui.isLoading()) // Since never attached once, user didn't have a chance to change the value, thus return internal directly
349+
if(!isAttached || ui == null || ui.isLoading()) { // Since never attached once, user didn't have a chance to change the value, thus return internal directly
339350
v.accept(internalValue);
351+
if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" getValue() returns internalValue = "+ internalValue);
352+
}
340353
else
341354
gatr("value", valueAsString -> {
355+
if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" getValue() returns from javascript value attribute = "+valueAsString);
342356
VALUE value = Value.stringToVal(valueAsString, this);
343357
v.accept(value);
344358
});
@@ -359,6 +373,8 @@ public THIS setValue(@Nullable VALUE v) {
359373
else newValJsonSafe = "\""+Value.escapeForJSON(newVal)+"\""; // json object or other primitive
360374

361375
String message = "{\"newValue\": "+newValJsonSafe+"}";
376+
if(App.isInDepthDebugging) AL.debug(this.getClass(), this.toPrintString()+" setValue() message -> "+message);
377+
362378
JsonObject jsonEl = JsonFile.parser.fromJson(message, JsonObject.class);
363379
ValueChangeEvent<THIS, VALUE> event = new ValueChangeEvent<>(message, jsonEl, _this, v, this.internalValue, true);
364380
this.internalValue = v;
@@ -411,8 +427,16 @@ public boolean isValuesEqual(VALUE val1, VALUE val2){
411427
/**
412428
* Executes the provided JavaScript code now, or later
413429
* if this component is not attached yet. <br>
430+
* <br>
431+
* Does nothing and directly returns if the UI is null or still loading, since
432+
* we assume that your provided JS code is strongly related to this component and that
433+
* it does a manipulation that was done in Java code before (like for example changing its HTML, a style or attribute)
434+
* to prevent duplicate operations. <br>
435+
* If that is not the case use {@link UI#executeJavaScriptSafely(String, String, int)} instead. <br>
436+
* <br>
414437
* Your code will be encapsulated in a try/catch block and errors logged to
415438
* the clients JavaScript console. <br>
439+
* <br>
416440
* A reference of this component will be added before your code, thus you can access
417441
* this component via the "comp" variable in your provided JavaScript code.
418442
*/
@@ -424,6 +448,7 @@ public THIS executeJS(String code){
424448
* @see #executeJS(String)
425449
*/
426450
public THIS executeJS(UI ui, String code){
451+
if(ui == null || ui.isLoading()) return _this;
427452
if(isAttached){
428453
ui.executeJavaScriptSafely(
429454
"try{"+
@@ -475,14 +500,17 @@ public THIS now(Consumer<THIS> code) {
475500
}
476501

477502
/**
478-
* Executes the provided code asynchronously in a new thread. <br>
503+
* Executes the provided code asynchronously in a thread from {@link App#executor} and returns directly.<br>
479504
* This function needs to be run inside UI context
480505
* since it executes {@link UI#get()}, otherwise {@link NullPointerException} is thrown. <br>
481506
* <br>
482507
* Note that your code-block will have access to the current UI,
483508
* which means that you can add/remove/change UI components without issues.
484509
* This also means that you will have to handle Thread-safety yourself
485-
* when doing things to the same component from multiple threads at the same time.
510+
* when doing things to the same component from multiple threads at the same time. <br>
511+
* <br>
512+
* Also note that your code is not allowed to run forever/block, since
513+
* sometimes added components get added (in the browser) only after your code was run, see {@link UI#pendingAppends} and {@link UI#access(Runnable)}.
486514
*
487515
* @param code the code to be executed asynchronously, contains this component as parameter.
488516
*/
@@ -837,6 +865,26 @@ public THIS visible(boolean b) {
837865
return _this;
838866
}
839867

868+
/**
869+
* Requires that the children have absolute width/height. <br>
870+
* If this component has width/height set, those are used, otherwise 100% as width/height is used.
871+
*/
872+
public THIS scrollable(boolean b) {
873+
String width = style.get("width").isEmpty() ? "100%" : style.get("width");
874+
String height = style.get("height").isEmpty() ? "100%" : style.get("height");
875+
scrollable(b, width, height, "", "");
876+
return _this;
877+
}
878+
879+
/**
880+
* Requires that the children have absolute width/height.
881+
*/
882+
public THIS scrollable(boolean b, String width, String height) {
883+
scrollable(b, width, height, "", "");
884+
return _this;
885+
}
886+
887+
840888
/**
841889
* Makes this component scrollable. <br>
842890
* Note that you must also set the width and height for this to work, <br>

src/main/java/com/osiris/desku/ui/HTTPServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public Response serve(IHTTPSession session) {
5757
return sendHTMLString(msg + "</body></html>\n");
5858
}
5959
try {
60-
AL.info("File: " + f);
60+
AL.debug(this.getClass(), "File: " + f);
6161
return sendFile(f);
6262
} catch (Exception e) {
6363
String err = "Failed to provide content for " + path + " due to an exception '" + e.getMessage() + "' (more details in the log). File: " + f;

0 commit comments

Comments
 (0)