Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
BattlefieldDuck committed Mar 8, 2024
1 parent 3baf2a1 commit 93be245
Show file tree
Hide file tree
Showing 15 changed files with 669 additions and 90 deletions.
14 changes: 11 additions & 3 deletions .github/workflows/dotnet-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@ jobs:
with:
dotnet-version: ${{ matrix.dotnet }}

- name: Setup node
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Install dependencies
- name: Install Node.js dependencies
run: npm install
working-directory: XtermBlazor/src

- name: Build Node.js project
run: npm run build
working-directory: XtermBlazor/src

- name: Restore .NET dependencies
run: dotnet restore XtermBlazor

- name: Build
- name: Build .NET project
run: dotnet build XtermBlazor --no-restore

# - name: Test
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/dotnet-publish-gpr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ jobs:
env:
NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Setup node
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Build
- name: Install Node.js dependencies
run: npm install
working-directory: XtermBlazor/src

- name: Build Node.js project
run: npm run build
working-directory: XtermBlazor/src

- name: Build .NET project
run: dotnet build XtermBlazor -c Release

- name: Create the package
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/dotnet-publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ jobs:
with:
dotnet-version: 8

- name: Setup node
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Build
- name: Install Node.js dependencies
run: npm install
working-directory: XtermBlazor/src

- name: Build Node.js project
run: npm run build
working-directory: XtermBlazor/src

- name: Build .NET project
run: dotnet build XtermBlazor -c Release

- name: Create the package
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,5 @@ $RECYCLE.BIN/

# XtermBlazor
XtermBlazor/wwwroot/*
!XtermBlazor/wwwroot/*.min.css
!XtermBlazor/wwwroot/*.min.js
6 changes: 6 additions & 0 deletions XtermBlazor.Demo.Server/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<input type="text" @bind-value="_input">
<input type="button" @onclick="Write" value="Write()" />
<input type="button" @onclick="WriteLine" value="WriteLine()" />
<input type="button" @onclick="Input" value="Input()" />

Cols: <input type="number" maxlength="4" size="4" @bind-value="_columns">
Rows: <input type="number" maxlength="4" size="4" @bind-value="_rows">
Expand Down Expand Up @@ -202,6 +203,11 @@ EventCallback Logs:
await _terminal.WriteLine(_input);
}

private async Task Input(MouseEventArgs args)
{
await _terminal.Input(_input);
}

private async Task HasSelection(MouseEventArgs args)
{
await _terminalEvent.WriteLine($"({++_eventId}) HasSelection(): {await _terminal.HasSelection()}");
Expand Down
6 changes: 6 additions & 0 deletions XtermBlazor.Demo.Wasm/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<input type="text" @bind-value="_input">
<input type="button" @onclick="Write" value="Write()" />
<input type="button" @onclick="WriteLine" value="WriteLine()" />
<input type="button" @onclick="Input" value="Input()" />

Cols: <input type="number" maxlength="4" size="4" @bind-value="_columns">
Rows: <input type="number" maxlength="4" size="4" @bind-value="_rows">
Expand Down Expand Up @@ -202,6 +203,11 @@ EventCallback Logs:
await _terminal.WriteLine(_input);
}

private async Task Input(MouseEventArgs args)
{
await _terminal.Input(_input);
}

private async Task HasSelection(MouseEventArgs args)
{
await _terminalEvent.WriteLine($"({++_eventId}) HasSelection(): {await _terminal.HasSelection()}");
Expand Down
14 changes: 11 additions & 3 deletions XtermBlazor/Xterm.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,19 @@ public void AttachCustomKeyEventHandler(Func<KeyboardEventArgs, bool> customKeyE
/// <returns></returns>
public ValueTask SetCustomKeyEventHandler(string customKeyEventHandler = "function (event) { return true; }")
{
return JSRuntime.InvokeVoidAsync("eval", $"{NAMESPACE_PREFIX}._terminals.get('{Id}').customKeyEventHandler = {customKeyEventHandler}");
return JSRuntime.InvokeVoidAsync("eval", $"{NAMESPACE_PREFIX}.getTerminalObjectById('{Id}').customKeyEventHandler = {customKeyEventHandler}");
}

/// <summary>
/// Attaches a custom wheel event handler which is run before keys are
/// processed, giving consumers of xterm.js control over whether to proceed
/// or cancel terminal wheel events.
/// </summary>
/// <remarks>
/// Note: The return boolean of <c>customKeyEventHandler</c> is ignored on Blazor Server.
/// If you wish to pass the return value to xterm.js,
/// please use <see cref="SetCustomWheelEventHandler"/> instead.
/// </remarks>
/// <param name="customWheelEventHandler">The custom WheelEvent handler to attach.
/// This is a function that takes a WheelEvent, allowing consumers to stop
/// propagation and/or prevent the default action. The function returns
Expand All @@ -263,17 +268,20 @@ public void AttachCustomWheelEventHandler(Func<WheelEventArgs, bool> customWheel
}

/// <summary>
/// Sets a custom wheels event handler which is run before keys are
/// Sets a custom wheels event handler which is run before keys are
/// processed, giving consumers of xterm.js control over whether to proceed
/// or cancel terminal wheel events.
/// </summary>
/// <remarks>
/// Note: If your project is using Blazor WebAssembly, you can use <see cref="AttachCustomWheelEventHandler"/> instead.
/// </remarks>
/// <param name="customWheelEventHandler">The custom WheelEvent handler to attach.
/// This is a function that takes a WheelEvent, allowing consumers to stop
/// propagation and/or prevent the default action. The function returns
/// whether the event should be processed by xterm.js.</param>
public ValueTask SetCustomWheelEventHandler(string customWheelEventHandler = "function (event) { return true; }")
{
return JSRuntime.InvokeVoidAsync("eval", $"{NAMESPACE_PREFIX}._terminals.get('{Id}').customWheelEventHandler = {customWheelEventHandler}");
return JSRuntime.InvokeVoidAsync("eval", $"{NAMESPACE_PREFIX}.getTerminalObjectById('{Id}').customWheelEventHandler = {customWheelEventHandler}");
}

/// <summary>
Expand Down
36 changes: 7 additions & 29 deletions XtermBlazor/XtermBlazor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,13 @@
</ItemGroup>

<ItemGroup>
<None Include="..\LICENSE">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="icon.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
<None Include="..\LICENSE" Pack="True" PackagePath="\" />
<None Include="..\README.md" Pack="True" PackagePath="\" />
<None Include="icon.png" Pack="True" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<None Remove="src\**\*" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
Expand All @@ -71,21 +66,4 @@
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<None Remove="src\**\*" />
</ItemGroup>

<ItemGroup>
<WebpackInputs Include="src\**\*.ts" Exclude="src\node_modules\**" />
</ItemGroup>

<Target Name="EnsureNpmRestored" Condition="!Exists('src/node_modules')">
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="src" Command="npm install" />
</Target>

<Target Name="RunWebpack" AfterTargets="ResolveReferences" Inputs="@(WebpackInputs)" Outputs="wwwroot\XtermBlazor.min.js" DependsOnTargets="EnsureNpmRestored">
<Exec WorkingDirectory="src" Command="npm run build" />
</Target>

</Project>
33 changes: 24 additions & 9 deletions XtermBlazor/src/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
# XtermBlazor

XtermBlazor is a Blazor library that integrates the xterm.js terminal emulator into Blazor applications.
XtermBlazor is a robust Blazor library that seamlessly integrates the xterm.js terminal emulator into Blazor applications, providing a rich and interactive command-line interface experience.

## Scripts Commands
## Usage

Here are some common npm scripts commands you can use:
If you make any changes to the `index.ts` file, you'll need to compile the TypeScript files to JavaScript.

- **npm install**: Install all the dependencies listed in `package.json`.
- **npm run build**: Compile TypeScript files to JavaScript and create `wwwroot/XtermBlazor.min.js` and `wwwroot/XtermBlazor.min.css`
- **npm run dev**: Compile TypeScript files to JavaScript in development mode and create `wwwroot/XtermBlazor.js` and `wwwroot/XtermBlazor.css`
### Install Dependencies

Install all necessary dependencies listed in the `package.json` file by running the following command in your terminal:

```sh
npm install
```

### Compile TypeScript Files

Run the following command to compile TypeScript files to JavaScript. This will create `wwwroot/XtermBlazor.min.js` and `wwwroot/XtermBlazor.min.css`:

```sh
npm run build
```

## Contributing
Contributions are welcome! Please feel free to submit pull requests or open issues.

We welcome contributions from the community! If you're interested in improving XtermBlazor, feel free to submit pull requests or open issues. Your input and feedback are greatly appreciated and will help us continue to improve and enhance the functionality of XtermBlazor.

## License
XtermBlazor is licensed under the MIT License. See the `LICENSE` file for more details.
```

XtermBlazor is licensed under the MIT License. For more details, please refer to the `LICENSE` file included in the repository.

Thank you for your interest in XtermBlazor. We look forward to your contributions and feedback!
89 changes: 50 additions & 39 deletions XtermBlazor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,35 @@ class XtermBlazor {
const terminal = new Terminal(options);

// Create Listeners
terminal.onBinary((data: string) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnBinary', id, data));
terminal.onBinary(data => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnBinary', id, data));
terminal.onCursorMove(() => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnCursorMove', id));
terminal.onData((data: string) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnData', id, data));
terminal.onKey((event: { key: string, domEvent: KeyboardEvent }) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnKey', id, { Key: event.key, DomEvent: this.parseKeyboardEvent(event.domEvent) }));
terminal.onData(data => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnData', id, data));
terminal.onKey(({ key, domEvent }) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnKey', id, { Key: key, DomEvent: this.parseKeyboardEvent(domEvent) }));
terminal.onLineFeed(() => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnLineFeed', id));
terminal.onScroll((newPosition: number) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnScroll', id, newPosition));
terminal.onScroll(newPosition => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnScroll', id, newPosition));
terminal.onSelectionChange(() => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnSelectionChange', id));
terminal.onRender((event: { start: number, end: number }) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnRender', id, event));
terminal.onResize((event: { cols: number, rows: number }) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnResize', id, { columns: event.cols, rows: event.rows }));
terminal.onTitleChange((title: string) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnTitleChange', id, title));
terminal.onRender(event => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnRender', id, event));
terminal.onResize(({ cols, rows }) => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnResize', id, { columns: cols, rows: rows }));
terminal.onTitleChange(title => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnTitleChange', id, title));
terminal.onBell(() => DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'OnBell', id));
terminal.attachCustomKeyEventHandler((event: KeyboardEvent) => {
terminal.attachCustomKeyEventHandler(event => {
try {
// Synchronous for Blazor WebAssembly apps only.
return DotNet.invokeMethod(this._ASSEMBLY_NAME, 'AttachCustomKeyEventHandler', id, this.parseKeyboardEvent(event));
} catch {
// Asynchronous for both Blazor Server and Blazor WebAssembly apps.
DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'AttachCustomKeyEventHandler', id, this.parseKeyboardEvent(event));
return this.getTerminalById(id).customKeyEventHandler?.call(event) ?? true;
return this.getTerminalObjectById(id).customKeyEventHandler?.call(event) ?? true;
}
});
terminal.attachCustomWheelEventHandler((event: WheelEvent) => {
terminal.attachCustomWheelEventHandler(event => {
try {
// Synchronous for Blazor WebAssembly apps only.
return DotNet.invokeMethod(this._ASSEMBLY_NAME, 'AttachCustomWheelEventHandler', id, event);
} catch {
// Asynchronous for both Blazor Server and Blazor WebAssembly apps.
DotNet.invokeMethodAsync(this._ASSEMBLY_NAME, 'AttachCustomWheelEventHandler', id, event);
return this.getTerminalById(id).customWheelEventHandler?.call(event) ?? true;
return this.getTerminalObjectById(id).customWheelEventHandler?.call(event) ?? true;
}
});

Expand Down Expand Up @@ -111,32 +111,32 @@ class XtermBlazor {
}

// Xterm Functions
getRows = (id: string) => this.getTerminalById(id).terminal.rows;
getCols = (id: string) => this.getTerminalById(id).terminal.cols;
getOptions = (id: string) => this.getTerminalById(id).terminal.options;
setOptions = (id: string, options: ITerminalOptions) => this.getTerminalById(id).terminal.options = options;
blur = (id: string) => this.getTerminalById(id).terminal.blur();
focus = (id: string) => this.getTerminalById(id).terminal.focus();
input = (id: string, data: string, wasUserInput: boolean) => this.getTerminalById(id).terminal.input(data, wasUserInput);
resize = (id: string, columns: number, rows: number) => this.getTerminalById(id).terminal.resize(columns, rows);
hasSelection = (id: string) => this.getTerminalById(id).terminal.hasSelection();
getSelection = (id: string) => this.getTerminalById(id).terminal.getSelection();
getSelectionPosition = (id: string) => this.getTerminalById(id).terminal.getSelectionPosition();
clearSelection = (id: string) => this.getTerminalById(id).terminal.clearSelection();
select = (id: string, column: number, row: number, length: number) => this.getTerminalById(id).terminal.select(column, row, length);
selectAll = (id: string) => this.getTerminalById(id).terminal.selectAll();
selectLines = (id: string, start: number, end: number) => this.getTerminalById(id).terminal.selectLines(start, end);
scrollLines = (id: string, amount: number) => this.getTerminalById(id).terminal.scrollLines(amount);
scrollPages = (id: string, pageCount: number) => this.getTerminalById(id).terminal.scrollPages(pageCount);
scrollToTop = (id: string) => this.getTerminalById(id).terminal.scrollToTop();
scrollToBottom = (id: string) => this.getTerminalById(id).terminal.scrollToBottom();
scrollToLine = (id: string, line: number) => this.getTerminalById(id).terminal.scrollToLine(line);
clear = (id: string) => this.getTerminalById(id).terminal.clear();
write = (id: string, data: string) => this.getTerminalById(id).terminal.write(data);
writeln = (id: string, data: string) => this.getTerminalById(id).terminal.writeln(data);
paste = (id: string, data: string) => this.getTerminalById(id).terminal.paste(data);
refresh = (id: string, start: number, end: number) => this.getTerminalById(id).terminal.refresh(start, end);
reset = (id: string) => this.getTerminalById(id).terminal.reset();
getRows = (id: string) => this.getTerminalById(id).rows;
getCols = (id: string) => this.getTerminalById(id).cols;
getOptions = (id: string) => this.getTerminalById(id).options;
setOptions = (id: string, options: ITerminalOptions) => this.getTerminalById(id).options = options;
blur = (id: string) => this.getTerminalById(id).blur();
focus = (id: string) => this.getTerminalById(id).focus();
input = (id: string, data: string, wasUserInput: boolean) => this.getTerminalById(id).input(data, wasUserInput);
resize = (id: string, columns: number, rows: number) => this.getTerminalById(id).resize(columns, rows);
hasSelection = (id: string) => this.getTerminalById(id).hasSelection();
getSelection = (id: string) => this.getTerminalById(id).getSelection();
getSelectionPosition = (id: string) => this.getTerminalById(id).getSelectionPosition();
clearSelection = (id: string) => this.getTerminalById(id).clearSelection();
select = (id: string, column: number, row: number, length: number) => this.getTerminalById(id).select(column, row, length);
selectAll = (id: string) => this.getTerminalById(id).selectAll();
selectLines = (id: string, start: number, end: number) => this.getTerminalById(id).selectLines(start, end);
scrollLines = (id: string, amount: number) => this.getTerminalById(id).scrollLines(amount);
scrollPages = (id: string, pageCount: number) => this.getTerminalById(id).scrollPages(pageCount);
scrollToTop = (id: string) => this.getTerminalById(id).scrollToTop();
scrollToBottom = (id: string) => this.getTerminalById(id).scrollToBottom();
scrollToLine = (id: string, line: number) => this.getTerminalById(id).scrollToLine(line);
clear = (id: string) => this.getTerminalById(id).clear();
write = (id: string, data: string) => this.getTerminalById(id).write(data);
writeln = (id: string, data: string) => this.getTerminalById(id).writeln(data);
paste = (id: string, data: string) => this.getTerminalById(id).paste(data);
refresh = (id: string, start: number, end: number) => this.getTerminalById(id).refresh(start, end);
reset = (id: string) => this.getTerminalById(id).reset();

/**
* This function invokes a specific function of an addon associated with a terminal.
Expand All @@ -147,7 +147,7 @@ class XtermBlazor {
* The function retrieves the terminal instance using the provided id, then retrieves the addon using the addonId. It then invokes the specified function on the addon with any additional arguments.
*/
invokeAddonFunction(id: string, addonId: string, functionName: string) {
const addon: { [key: string]: any } = this.getTerminalById(id).addons.get(addonId);
const addon: { [key: string]: any } = this.getTerminalObjectById(id).addons.get(addonId);
return addon[functionName](...arguments[3]);
}

Expand All @@ -158,7 +158,7 @@ class XtermBlazor {
*
* The function retrieves the terminal instance from the internal terminals list using the provided id. If no terminal is found, it throws an error.
*/
getTerminalById(id: string): ITerminalObject {
getTerminalObjectById(id: string): ITerminalObject {
const terminal = this._terminals.get(id);

if (!terminal) {
Expand All @@ -168,6 +168,17 @@ class XtermBlazor {
return terminal;
}

/**
* This function retrieves a Terminal instance by its unique identifier.
* @param id {string} - The unique identifier for the terminal.
* @returns {Terminal} - The Terminal instance associated with the provided id.
*
* The function retrieves the Terminal instance by calling the `getTerminalObjectById` method with the provided id. If no terminal is found, it throws an error.
*/
getTerminalById(id: string): Terminal {
return this.getTerminalObjectById(id).terminal;
}

/**
* Converts KeyboardEvent properties to a key-value object.
* @param event - The KeyboardEvent to parse.
Expand Down
Loading

0 comments on commit 93be245

Please sign in to comment.