Skip to content

Commit 3a0b69c

Browse files
Dev UI Workspace changes for Chappie
Signed-off-by: Phillip Kruger <phillip.kruger@gmail.com>
1 parent 30f2f55 commit 3a0b69c

File tree

5 files changed

+167
-70
lines changed

5 files changed

+167
-70
lines changed

core/runtime/src/main/java/io/quarkus/runtime/logging/DecorateStackUtil.java

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.nio.file.Path;
77
import java.util.ArrayDeque;
88
import java.util.ArrayList;
9+
import java.util.Comparator;
910
import java.util.Deque;
1011
import java.util.List;
1112

@@ -40,19 +41,63 @@ public static String getDecoratedString(Path srcMainJava, StackTraceElement stac
4041
if (lineNumber > 0 && srcMainJava != null) {
4142
String fullJavaFileName = getFullPath(stackTraceElement.getClassName(), stackTraceElement.getFileName());
4243
Path f = srcMainJava.resolve(fullJavaFileName);
43-
try {
44-
List<String> contextLines = DecorateStackUtil.getRelatedLinesInSource(f, lineNumber, 2);
45-
if (contextLines != null) {
46-
String header = "Exception in " + stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber();
47-
return header + "\n" + String.join("\n", contextLines);
48-
}
49-
} catch (IOException e) {
50-
// Could not find the source for some reason. Just return nothing then
44+
return getDecoratedString(stackTraceElement, f, lineNumber);
45+
}
46+
return null;
47+
}
48+
49+
public static String getDecoratedString(StackTraceElement stackTraceElement, List<Path> workspacePaths) {
50+
if (stackTraceElement == null || workspacePaths == null || workspacePaths.isEmpty()) {
51+
return null;
52+
}
53+
54+
int lineNumber = stackTraceElement.getLineNumber();
55+
56+
if (lineNumber > 0) {
57+
// Convert the class name to a relative path: io.quarkiverse.chappie.sample.ChappieSimulateResource -> io/quarkiverse/chappie/sample/ChappieSimulateResource.java
58+
String fullJavaFileName = getFullPath(stackTraceElement.getClassName(), stackTraceElement.getFileName());
59+
Path affectedPath = findAffectedPath(fullJavaFileName, workspacePaths);
60+
return getDecoratedString(stackTraceElement, affectedPath, lineNumber);
61+
}
62+
return null;
63+
}
64+
65+
public static String getDecoratedString(StackTraceElement stackTraceElement, Path f, int lineNumber) {
66+
try {
67+
List<String> contextLines = DecorateStackUtil.getRelatedLinesInSource(f, lineNumber, 2);
68+
if (contextLines != null) {
69+
String header = "Exception in " + stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber();
70+
return header + "\n" + String.join("\n", contextLines);
5171
}
72+
} catch (IOException e) {
73+
// Could not find the source for some reason. Just return nothing then
5274
}
5375
return null;
5476
}
5577

78+
public static Path findAffectedPath(String name, List<Path> workspacePaths) {
79+
// Search the workspace paths for a match that ends with the fullJavaFileName
80+
return workspacePaths.stream()
81+
.filter(path -> path.toString().endsWith(convertNameToRelativePath(name)))
82+
.sorted(Comparator.comparingInt(path -> path.toString().contains("src/main/java") ? 0 : 1)) // Prioritize src/main/java
83+
.findFirst()
84+
.orElse(null);
85+
}
86+
87+
private static String convertNameToRelativePath(String name) {
88+
if (name == null || name.isEmpty()) {
89+
throw new IllegalArgumentException("Class name cannot be null or empty");
90+
}
91+
if (!isPathString(name)) {
92+
return name.replace('.', '/') + ".java";
93+
}
94+
return name;
95+
}
96+
97+
private static boolean isPathString(String name) {
98+
return name.contains("/") && name.endsWith(".java");
99+
}
100+
56101
private static List<String> getRelatedLinesInSource(Path filePath, int lineNumber, int contextRange) throws IOException {
57102
if (Files.exists(filePath)) {
58103
List<String> resultLines = new ArrayList<>();
@@ -90,4 +135,4 @@ private static String getFullPath(String fullClassName, String fileName) {
90135
return path + "/" + fileName;
91136
}
92137

93-
}
138+
}

extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/WorkspaceProcessor.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.List;
1717
import java.util.Map;
1818
import java.util.Optional;
19+
import java.util.concurrent.CompletionStage;
1920
import java.util.regex.Pattern;
2021
import java.util.stream.Collectors;
2122

@@ -117,8 +118,7 @@ void createDefaultWorkspaceActions(BuildProducer<WorkspaceActionBuildItem> works
117118
ActionBuilder actionBuilder = Action.actionBuilder()
118119
.label("Preview")
119120
.function((t) -> {
120-
Map params = (Map) t;
121-
return params.get("content"); // We just return the content, we will markup in the UI
121+
return t; // We just return the content, we will markup in the UI
122122
})
123123
.display(Display.split)
124124
.displayType(DisplayType.markdown)
@@ -156,15 +156,18 @@ void createBuildTimeActions(Optional<WorkspaceBuildItem> workspaceBuildItem,
156156
.collect(Collectors.toList());
157157
});
158158

159-
buildItemActions.addAction("executeAction", (t) -> {
160-
159+
buildItemActions.addAction("executeAction", (Map<String, String> t) -> {
161160
String actionId = t.get("actionId");
162-
163161
if (actionId != null) {
164162
Path path = Path.of(URI.create(t.get("path")));
165163
Action actionToExecute = actionMap.get(actionId);
166164
Path convertedPath = (Path) actionToExecute.getPathConverter().apply(path);
167-
return new WorkspaceActionResult(convertedPath, actionToExecute.getFunction().apply(t));
165+
Object result = actionToExecute.getFunction().apply(t);
166+
if (result instanceof CompletionStage<?> stage) {
167+
return stage.thenApply(res -> new WorkspaceActionResult(convertedPath, res));
168+
} else {
169+
return new WorkspaceActionResult(convertedPath, result);
170+
}
168171
}
169172
return null;
170173
});

extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-workspace.js

Lines changed: 93 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import '@vaadin/confirm-dialog';
1515
import '@vaadin/progress-bar';
1616
import MarkdownIt from 'markdown-it';
1717
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
18-
import { dialogFooterRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
18+
import { dialogHeaderRenderer, dialogFooterRenderer, dialogRenderer } from '@vaadin/dialog/lit.js';
1919
import { observeState } from 'lit-element-state';
2020
import { themeState } from 'theme-state';
2121
import { notifier } from 'notifier';
@@ -83,11 +83,9 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
8383
_filteredActions: {state: true},
8484
_selectedWorkspaceItem: {state: true},
8585
_changeToWorkspaceItem: {state: true},
86-
_actionResultContent: {state:true},
87-
_actionResultDisplay: {state:true},
88-
_actionResultDisplayType: {state:true},
86+
_actionResult: {state: true},
8987
_showActionProgress: {state: true},
90-
_confirmDialogOpened: {state: true}
88+
_confirmDialogOpened: {state: true},
9189
};
9290

9391
constructor() {
@@ -122,9 +120,8 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
122120
this._workspaceActions = [];
123121
this._filteredActions = this._workspaceActions;
124122
this._clearActionResult();
125-
126-
this._loadWorkspaceItems();
127123
this._loadWorkspaceActions();
124+
this._loadWorkspaceItems();
128125
}
129126

130127
disconnectedCallback() {
@@ -193,14 +190,16 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
193190
<vaadin-button title="Copy" slot="prefix" theme="icon" aria-label="Copy" @click="${this._copySelectedWorkspaceItem}">
194191
<vaadin-icon icon="font-awesome-solid:copy"></vaadin-icon>
195192
</vaadin-button>
193+
194+
195+
${this._renderActions()}
196+
196197
<qui-ide-link slot="suffix" title="Open in IDE" style="cursor: pointer;"
197198
fileName="${this._selectedWorkspaceItem.path}"
198199
lineNumber="0"
199200
noCheck>
200201
<vaadin-icon icon="font-awesome-solid:up-right-from-square"></vaadin-icon>
201202
</qui-ide-link>
202-
203-
${this._renderActions()}
204203
205204
<vaadin-tabs slot="tabs">
206205
<vaadin-tab id="${this._selectedWorkspaceItem.path}" title="${this._selectedWorkspaceItem.path}">${this._selectedWorkspaceItem.name.split('/').pop()}</vaadin-tab>
@@ -217,7 +216,7 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
217216
}
218217

219218
_renderResultSplitView(){
220-
if(this._actionResultContent && this._actionResultDisplay === "split"){
219+
if(this._actionResult && this._actionResult.content && this._actionResult.display === "split"){
221220
return html`<detail-content style="width: 50%;">
222221
<div class="actionSplitScreen">
223222
<div class="actionButtonBar">
@@ -233,36 +232,54 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
233232
}
234233

235234
_renderResultDialog(){
236-
if(this._actionResultContent && this._actionResultDisplay === "dialog"){
237-
return html`<vaadin-dialog
235+
if(this._actionResult && this._actionResult.content && this._actionResult.display === "dialog"){
236+
return html`<vaadin-dialog style="min-width=50vw;"
237+
header-title="${this._actionResult?.name ?? this._actionResult?.path}"
238238
resizable
239239
draggable
240-
.opened=true
240+
.opened=${true}
241+
${dialogHeaderRenderer(
242+
() => html`
243+
<vaadin-button theme="tertiary" @click="${this._clearActionResult}">
244+
<vaadin-icon icon="font-awesome-solid:xmark"></vaadin-icon>
245+
</vaadin-button>
246+
`,
247+
[]
248+
)}
241249
${dialogRenderer(this._renderActionResult, [])}
250+
${dialogFooterRenderer(
251+
() => html`
252+
<vaadin-button theme="primary" @click="${this._saveActionResult}">
253+
Save
254+
</vaadin-button>
255+
<vaadin-button theme="tertiary" @click="${this._copyActionResult}">Copy</vaadin-button>
256+
`,
257+
[]
258+
)}
242259
></vaadin-dialog>`;
243260
}
244261
}
245262

246263
_renderActionResult(){
247-
if(this._actionResultContent && this._actionResultDisplayType === "raw"){
248-
return html`${this._actionResultContent}`;
249-
}else if(this._actionResultContent && this._actionResultDisplayType === "code"){
264+
if(this._actionResult && this._actionResult.content && this._actionResult.displayType === "raw"){
265+
return html`${this._actionResult.content}`;
266+
}else if(this._actionResult && this._actionResult.content && this._actionResult.displayType === "code"){
250267
// TODO: We can not assume the mode is the same as the input
251268
// Maybe return name|content ?
252269
return html`<qui-code-block id="code" class='codeBlock'
253-
mode='${this._getMode(this._selectedWorkspaceItem.name)}'
270+
mode='${this._getMode(this._actionResult?.name ?? this._actionResult?.path)}'
254271
theme='${themeState.theme.name}'
255-
.content='${this._actionResultContent}'
272+
.content='${this._actionResult.content}'
256273
showLineNumbers>
257274
</qui-code-block>`;
258-
}else if(this._actionResultContent && this._actionResultDisplayType === "markdown"){
259-
const htmlContent = this.md.render(this._actionResultContent);
275+
}else if(this._actionResult && this._actionResult.content && this._actionResult.displayType === "markdown"){
276+
const htmlContent = this.md.render(this._actionResult.content);
260277
return html`${unsafeHTML(htmlContent)}`;
261-
}else if(this._actionResultContent && this._actionResultDisplayType === "html"){
262-
return html`${unsafeHTML(this._actionResultContent)}`;
263-
}else if(this._actionResultContent && this._actionResultDisplayType === "image"){
264-
let imgurl = `data:image/png;base64,${this._actionResultContent}`;
265-
return html`<img src="${imgurl}" alt="${this._selectedWorkspaceItem.name}" style="max-width: 100%;"/>`;
278+
}else if(this._actionResult && this._actionResult.content && this._actionResult.displayType === "html"){
279+
return html`${unsafeHTML(this._actionResult.content)}`;
280+
}else if(this._actionResult && this._actionResult.content && this._actionResult.displayType === "image"){
281+
let imgurl = `data:image/png;base64,${this._actionResult.content}`;
282+
return html`<img src="${imgurl}" alt="${this._actionResult?.name ?? this._actionResult?.path}" style="max-width: 100%;"/>`;
266283
}
267284
}
268285

@@ -366,24 +383,25 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
366383
if(codeElement){
367384
newWorkspaceItemValue = codeElement.getAttribute('value');
368385
}
369-
370386
this.jsonRpc.executeAction({actionId:actionId,
371387
name:this._selectedWorkspaceItem.name,
372388
path:this._selectedWorkspaceItem.path,
373389
content:newWorkspaceItemValue,
374390
type:this._selectedWorkspaceItem.type}).then(jsonRpcResponse => {
391+
375392
if(e.detail.value.display === "notification"){
376393
notifier.showInfoMessage(jsonRpcResponse.result.result);
377394
}else if(e.detail.value.display === "replace"){
378-
// TODO: This does not take Markdown into context....
379-
// TODO: Use result
380-
this._selectedWorkspaceItem.content = jsonRpcResponse.result.result;
395+
this._selectedWorkspaceItem.content = jsonRpcResponse.result.result.content;
381396
this._selectedWorkspaceItem.type = e.detail.value.displayType;
382-
//this._selectedWorkspaceItem.path =
397+
this._selectedWorkspaceItem.path = jsonRpcResponse.result.path;
398+
this._selectedWorkspaceItem.isDirty = true;
383399
}else if(e.detail.value.display !== "nothing"){
384-
this._actionResultContent = jsonRpcResponse.result.result;
385-
this._actionResultDisplay = e.detail.value.display;
386-
this._actionResultDisplayType = e.detail.value.displayType;
400+
this._actionResult = jsonRpcResponse.result.result;
401+
this._actionResult.name = this._actionResult.path;
402+
this._actionResult.path = jsonRpcResponse.result.path;
403+
this._actionResult.display = e.detail.value.display;
404+
this._actionResult.displayType = e.detail.value.displayType;
387405
}
388406
this._showActionProgress = false;
389407
});
@@ -423,18 +441,9 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
423441
}
424442

425443
_saveSelectedWorkspaceItem(){
426-
427444
let newWorkspaceItemValue = this._getChangedContent();
428445
if(newWorkspaceItemValue){
429-
this.jsonRpc.saveWorkspaceItemContent({content:newWorkspaceItemValue, path:this._selectedWorkspaceItem.path}).then(jsonRpcResponse => {
430-
if(jsonRpcResponse.result.success){
431-
notifier.showInfoMessage(jsonRpcResponse.result.path + " saved successfully");
432-
this._selectedWorkspaceItem = { ...this._selectedWorkspaceItem, content: newWorkspaceItemValue, isDirty: false };
433-
super.forceRestart();
434-
}else {
435-
notifier.showErrorMessage(jsonRpcResponse.result.path + " NOT saved. " + jsonRpcResponse.result.errorMessage);
436-
}
437-
});
446+
this._saveContent(newWorkspaceItemValue, this._selectedWorkspaceItem.path);
438447
}
439448
}
440449

@@ -445,6 +454,35 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
445454
if(changedContent)content = changedContent;
446455

447456
const path = this._selectedWorkspaceItem?.path;
457+
this._copyContent(content, path);
458+
}
459+
460+
_saveActionResult(){
461+
if(this._actionResult && this._actionResult.content){
462+
this._saveContent(this._actionResult.content, this._actionResult.path, false);
463+
}
464+
}
465+
466+
_copyActionResult(){
467+
if(this._actionResult && this._actionResult.content){
468+
this._copyContent(this._actionResult.content, this._actionResult.path);
469+
}
470+
}
471+
472+
_saveContent(content, path, select=true){
473+
this.jsonRpc.saveWorkspaceItemContent({content:content, path:path}).then(jsonRpcResponse => {
474+
if(jsonRpcResponse.result.success){
475+
notifier.showInfoMessage(jsonRpcResponse.result.path + " saved successfully");
476+
if(select) this._selectedWorkspaceItem = { ...this._selectedWorkspaceItem, content: content, isDirty: false };
477+
//super.forceRestart();
478+
}else {
479+
notifier.showErrorMessage(jsonRpcResponse.result.path + " NOT saved. " + jsonRpcResponse.result.errorMessage);
480+
}
481+
});
482+
483+
}
484+
485+
_copyContent(content, path){
448486
if (!content) {
449487
notifier.showWarningMessage(path + " has no content");
450488
return;
@@ -469,10 +507,7 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
469507
}
470508

471509
_clearActionResult(){
472-
this._actionResultContent = null;
473-
this._actionResultDisplay = null;
474-
this._actionResultDisplayType = null;
475-
this._showActionProgress = false;
510+
this._actionResult = null;
476511
}
477512

478513
_selectWorkspaceItem(workspaceItem){
@@ -501,9 +536,11 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
501536
this._filterActions(workspaceItem.name);
502537

503538
this._clearActionResult();
539+
this._showActionProgress = false;
504540
}
505541

506542
shouldConfirmAwayNavigation(){
543+
if(this._selectedWorkspaceItem.isDirty) return true;
507544
let changedContent = this._getChangedContent();
508545
if(changedContent){
509546
return true;
@@ -513,11 +550,15 @@ export class QwcWorkspace extends observeState(QwcHotReloadElement) {
513550

514551
_getChangedContent(){
515552
if(this._selectedWorkspaceItem.content){
516-
let codeElement = this.shadowRoot.getElementById('code');
517-
if(codeElement){
518-
let newWorkspaceItemValue = codeElement.getAttribute('value');
519-
if(newWorkspaceItemValue!==this._selectedWorkspaceItem.content){
520-
return newWorkspaceItemValue;
553+
if(this._selectedWorkspaceItem.isDirty){
554+
return this._selectedWorkspaceItem.content;
555+
}else {
556+
let codeElement = this.shadowRoot.getElementById('code');
557+
if(codeElement){
558+
let newWorkspaceItemValue = codeElement.getAttribute('value');
559+
if(newWorkspaceItemValue!==this._selectedWorkspaceItem.content){
560+
return newWorkspaceItemValue;
561+
}
521562
}
522563
}
523564
}

0 commit comments

Comments
 (0)