diff --git a/Nodexr/Shared/Components/ContentEditableDiv.razor b/Nodexr/Shared/Components/ContentEditableDiv.razor
new file mode 100644
index 00000000..ba531023
--- /dev/null
+++ b/Nodexr/Shared/Components/ContentEditableDiv.razor
@@ -0,0 +1,42 @@
+@inject IJSRuntime JS;
+
+
+
+@code {
+
+ [Parameter]
+ public string Text { get; set; }
+
+ [Parameter]
+ public string CssClass { get; set; }
+
+ [Parameter]
+ public EventCallback TextChanged { get; set; }
+
+ ElementReference divElement;
+
+ protected string textToDisplay;
+
+ protected override void OnInitialized()
+ {
+ //Text = Text.Replace(Environment.NewLine, "
");
+ }
+
+ //send initial text (if any) to javascript to place in the div
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ {
+ await JS.InvokeVoidAsync("contentEditable.initContentEditable", divElement, DotNetObjectReference.Create(this), Text);
+ }
+ }
+
+ //receive input text from javascript and invoke callback to parent component
+ [JSInvokable]
+ public async Task GetUpdatedTextFromJavascript(string textFromJavascript)
+ {
+ Text = textFromJavascript;
+ await TextChanged.InvokeAsync(textFromJavascript);
+ }
+}
diff --git a/Nodexr/Shared/Components/OutputDisplay.razor b/Nodexr/Shared/Components/OutputDisplay.razor
index c0a5de81..ade66b76 100644
--- a/Nodexr/Shared/Components/OutputDisplay.razor
+++ b/Nodexr/Shared/Components/OutputDisplay.razor
@@ -9,46 +9,55 @@
Output:
-
@foreach (var segment in @NodeHandler.CachedOutput.Contents)
- {@*This must not be surrounded by whitespace*@}
+
@if (isEditing)
+ {}
+ else
+ {
+ @foreach (var segment in @NodeHandler.CachedOutput.Contents)
+ {@*This must not be surrounded by whitespace*@}
+ }
+
-
-
-
+
+
+
@functions{
+ bool isEditing = false;
+
protected override void OnInitialized()
{
NodeHandler.OutputChanged += (sender, e) => StateHasChanged();
}
- private async Task OnEditButtonClick()
+ private void OnEditButtonClick()
{
- var modalParameters = new ModalParameters();
- modalParameters.Add(nameof(EditRegexDialog.previousRegex), NodeHandler.CachedOutput.Expression);
+ isEditing = !isEditing;
+ }
- var modal = ModalService.Show("Custom Expression", modalParameters);
- var result = await modal.Result;
- if (result.Cancelled)
- {
- Console.WriteLine("Modal was cancelled");
- }
- else if (result.Data is string customRegex)
- {
- Console.WriteLine("Custom Regex: " + customRegex);
- NodeHandler.TryCreateTreeFromRegex(customRegex);
- }
+ private void OnEditSubmitted(string newExpression)
+ {
+ isEditing = false;
+ NodeHandler.TryCreateTreeFromRegex(newExpression);
+ }
+
+ private void OnEditCancelled()
+ {
+ isEditing = false;
+ StateHasChanged();
}
private async Task OnCreateLinkButtonClick()
{
var urlParams = new Dictionary
- {
- { "parse", NodeHandler.CachedOutput.Expression }
- };
+ {
+ { "parse", NodeHandler.CachedOutput.Expression }
+ };
if (RegexReplaceHandler.SearchText != RegexReplaceHandler.DefaultSearchText)
{
diff --git a/Nodexr/Shared/Components/OutputDisplaySegment.razor b/Nodexr/Shared/Components/OutputDisplaySegment.razor
index fdf3858b..abd2d36c 100644
--- a/Nodexr/Shared/Components/OutputDisplaySegment.razor
+++ b/Nodexr/Shared/Components/OutputDisplaySegment.razor
@@ -1,7 +1,7 @@
@implements IDisposable
@inject INodeHandler NodeHandler
- NodeHandler.SelectNode(Segment.Node))"
title="@Segment.Node.Title"
diff --git a/Nodexr/Shared/Components/OutputEditor.razor b/Nodexr/Shared/Components/OutputEditor.razor
new file mode 100644
index 00000000..e2274e36
--- /dev/null
+++ b/Nodexr/Shared/Components/OutputEditor.razor
@@ -0,0 +1,50 @@
+
+
+
+
+ Input your own regular expression here to convert it to a node tree.
+
+
+
+
+
+@code {
+
+ string expression;
+
+ [Parameter] public string StartExpression
+ {
+ set => expression = value;
+ }
+
+ [Parameter] public EventCallback OnSubmitted { get; set; }
+ [Parameter] public EventCallback OnCanceled { get; set; }
+
+ protected async Task OnKeyPress(KeyboardEventArgs e)
+ {
+ if(e.Key == "Enter" && !e.ShiftKey)
+ {
+ await Submit();
+ }
+ }
+
+ protected async Task OnKeyUp(KeyboardEventArgs e)
+ {
+ if (e.Key == "Escape")
+ {
+ await Cancel();
+ }
+ }
+
+ protected async Task Submit()
+ {
+ await OnSubmitted.InvokeAsync(expression);
+ }
+
+ protected async Task Cancel()
+ {
+ await OnCanceled.InvokeAsync(null);
+ }
+}
diff --git a/Nodexr/Shared/Components/ToastButton.razor b/Nodexr/Shared/Components/ToastButton.razor
new file mode 100644
index 00000000..daa9abe4
--- /dev/null
+++ b/Nodexr/Shared/Components/ToastButton.razor
@@ -0,0 +1,22 @@
+@if (!string.IsNullOrEmpty(Info))
+{
+ @Info
+}
+
+
+@code {
+ [Parameter] public Action OnClick { get; set; }
+ [Parameter] public string ButtonText { get; set; }
+ [Parameter] public string Info { get; set; }
+
+ private bool hasBeenPressed = false;
+
+ private void OnButtonClicked()
+ {
+ hasBeenPressed = true;
+ OnClick?.Invoke();
+ }
+
+ public static RenderFragment GetRenderFragment(Action onClick, string buttonText, string info = null)
+ => @;
+}
\ No newline at end of file
diff --git a/Nodexr/Shared/MainLayout.razor b/Nodexr/Shared/MainLayout.razor
index 8c734fe1..95891918 100644
--- a/Nodexr/Shared/MainLayout.razor
+++ b/Nodexr/Shared/MainLayout.razor
@@ -11,7 +11,7 @@
*@
+ Timeout="13"/>
@Body
diff --git a/Nodexr/Shared/Services/NodeHandler.cs b/Nodexr/Shared/Services/NodeHandler.cs
index e98843ba..f6190477 100644
--- a/Nodexr/Shared/Services/NodeHandler.cs
+++ b/Nodexr/Shared/Services/NodeHandler.cs
@@ -28,8 +28,9 @@ public interface INodeHandler
void ForceRefreshNoodles();
void SelectNode(INode node);
void DeselectAllNodes();
- bool TryCreateTreeFromRegex(string regex);
+ void TryCreateTreeFromRegex(string regex);
bool IsNodeSelected(INode node);
+ void RevertPreviousParse();
}
public class NodeHandler : INodeHandler
@@ -74,6 +75,9 @@ private set
private readonly IToastService toastService;
+ //Stores the previous tree from before the most recent parse, so that the parse can be reverted.
+ private NodeTree treePrevious;
+
public NodeHandler(NavigationManager navManager, IToastService toastService)
{
this.toastService = toastService;
@@ -98,25 +102,46 @@ public NodeHandler(NavigationManager navManager, IToastService toastService)
///
/// The regular expression to parse, in string format
/// Whether or not the parse attempt succeeded
- public bool TryCreateTreeFromRegex(string regex)
+ public void TryCreateTreeFromRegex(string regex)
{
var parseResult = RegexParser.Parse(regex);
if (parseResult.Success)
{
+ treePrevious = tree;
Tree = parseResult.Value;
ForceRefreshNodeGraph();
OnOutputChanged(this, EventArgs.Empty);
- return true;
+
+ if(CachedOutput.Expression == regex)
+ {
+ var fragment = Components.ToastButton.GetRenderFragment(RevertPreviousParse, "Revert to previous");
+ toastService.ShowSuccess(fragment, "Converted to node tree successfully");
+ }
+ else
+ {
+ var fragment = Components.ToastButton.GetRenderFragment(
+ RevertPreviousParse,
+ "Revert to previous",
+ "Your expression was parsed, but the resulting output is slighty different to your input. " +
+ "This is most likely due to a simplification that has been performed automatically.\n");
+ toastService.ShowInfo(fragment, "Converted to node tree");
+ }
}
else
{
toastService.ShowError(parseResult.Error.ToString(), "Couldn't parse input");
Console.WriteLine("Couldn't parse input: " + parseResult.Error);
- return false;
}
}
+ public void RevertPreviousParse()
+ {
+ Tree = treePrevious;
+ ForceRefreshNodeGraph();
+ OnOutputChanged(this, EventArgs.Empty);
+ }
+
public void ForceRefreshNodeGraph()
{
OnRequireNodeGraphRefresh?.Invoke(this, EventArgs.Empty);
diff --git a/Nodexr/wwwroot/css/node.css b/Nodexr/wwwroot/css/node.css
index c08e2dd1..e7899997 100644
--- a/Nodexr/wwwroot/css/node.css
+++ b/Nodexr/wwwroot/css/node.css
@@ -18,6 +18,7 @@
padding: 2px 4px 0px 4px;
/*margin-bottom: 0px;*/
border-radius: 8px 8px 0px 0px;
+ color: var(--col-text-invert);
}
.node .node-title.collapsed {
border-radius: 8px
@@ -34,13 +35,14 @@
border: none;
font-size: 0.8rem;
padding: 0 0 0 7px;
+ color: inherit;
}
.node .icon-button {
position: relative;
background: none;
border: none;
- color: var(--col-text-strong2);
+ color: inherit;
font-size: 0.9rem;
padding: 1px 0 0 0;
outline: 0 !important;
@@ -206,16 +208,9 @@
padding-left: 7px;
}
-
-
-
.add-button {
position: relative;
margin: 5px 0px 0px 0px;
- background-color: rgba(200,200,256,0.09);
- border: none;
- border-radius: 6px;
- color: var(--col-text-medium);
font-size: 0.9rem;
}
diff --git a/Nodexr/wwwroot/css/site.css b/Nodexr/wwwroot/css/site.css
index b60282ae..e35b947e 100644
--- a/Nodexr/wwwroot/css/site.css
+++ b/Nodexr/wwwroot/css/site.css
@@ -36,6 +36,7 @@
--col-text-strong2: #eaeaea;
--col-text-medium: #d0d0d0;
--col-text-subtle: #acacac;
+ --col-text-invert: #262626;
--col-field: hsl(218, 12%, 28%);
--col-field-editable: hsl(218, 10%, 28%);
/**/
@@ -257,26 +258,34 @@ input {
margin: 5px;
}
- .output-regex-container :first-child {
+ .output-regex-container > *:first-child {
border-radius: 5px 0 0 5px;
}
- .output-regex-container :last-child {
+ .output-regex-container > *:last-child {
border-radius: 0 5px 5px 0;
}
.output-regex {
+ border: none;
background-color: var(--col-field);
color: var(--col-text-medium);
- border: none;
+ min-width: 250px;
+ padding: 0px 5px;
+ height: auto;
+}
+
+.output-regex-container > .output-regex:focus-within {
+ box-shadow: inset 0 0 4px 1px #0078f5;
+}
+
+.output-regex-text {
font-size: 30px;
font-family: Consolas, monospace;
line-height: 34px;
- min-width: 250px;
- padding: 0px 5px;
white-space: pre-wrap;
word-break: break-all;
- height: auto;
+ outline: 0;
}
.output-segment {
@@ -296,12 +305,44 @@ input {
min-width: unset;
background-color: hsl(223, 13%, 44%);
font-size: 24px;
+ border-radius: 0;
}
.output-regex-button:hover {
background-color: hsl(222, 14%, 22%);
}
+.output-edit-textarea {
+ position: absolute;
+ resize: none;
+ background-color: transparent;
+ color: inherit;
+ overflow: hidden;
+ border: none;
+ padding: 0;
+ width: 100%;
+/* height: 100%;*/
+}
+
+.output-edit-prompt {
+ font-size: 1.1em;
+ position: absolute;
+ width: 20em;
+ background-color: rgba(0, 0, 0, 0.8);
+ border-radius: 5px;
+ padding: 0.2em 0.2em 0.2em 0.5em;
+ margin-top: 6px;
+ margin-left: -5px;
+}
+
+ .output-edit-prompt button {
+ margin: 2px;
+ float: right;
+ }
+ .output-edit-prompt button:hover {
+ background-color: #c0c0ff60;
+ }
+
.search-text-highlight, .search-textarea {
padding: 3px;
/*letter-spacing: 1px;*/
@@ -363,6 +404,28 @@ mark {
padding: 0;
}
+.toast-button {
+ background-color: transparent;
+ font-size: large;
+ border: 1px solid white;
+ color: white;
+}
+
+.toast-button:hover:enabled {
+ background-color: #ffffff44;
+}
+
+.toast-button[disabled] {
+ color: #ffffffAA;
+}
+
+button {
+ background-color: rgba(200,200,256,0.2);
+ border: none;
+ border-radius: 6px;
+ color: var(--col-text-medium);
+}
+
button:focus {
outline: none;
}
diff --git a/Nodexr/wwwroot/index.html b/Nodexr/wwwroot/index.html
index c5bad6b6..84af50f1 100644
--- a/Nodexr/wwwroot/index.html
+++ b/Nodexr/wwwroot/index.html
@@ -99,6 +99,7 @@
+