├── .gitignore ├── .travis.yml ├── Assets └── Integrative.png ├── LICENSE ├── README.md ├── src ├── Boilerplate │ ├── Program.cs │ ├── Wiki │ │ ├── ComponentPropertyExample.cs │ │ ├── ComposingComponent.cs │ │ ├── ConditionalRenderComponent.cs │ │ ├── DocumentContextExample.cs │ │ ├── HttpContextExample.cs │ │ ├── LoopComponent.cs │ │ ├── RedBoxExample.cs │ │ ├── SimpleComponent.cs │ │ ├── UserInputComponent.cs │ │ └── UserTextComponent.cs │ └── WikiExamples.csproj ├── LaraClient.sln ├── LaraClient │ ├── .eslintignore │ ├── .eslintrc │ ├── .prettierrc │ ├── LaraClient.njsproj │ ├── README.md │ ├── dist │ │ └── lara-client.js.LICENSE.txt │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── Autocomplete.ts │ │ ├── Blocker.ts │ │ ├── ContentInterfaces.ts │ │ ├── DeltaInterfaces.ts │ │ ├── Initializer.ts │ │ ├── InputCollector.ts │ │ ├── RegisteredEvents.ts │ │ ├── Sequencer.ts │ │ ├── SocketEvents.ts │ │ ├── Worker.ts │ │ ├── blockUI.js │ │ ├── custom.d.ts │ │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── LaraDocumentation │ ├── Content │ │ └── Welcome.aml │ ├── ContentLayout.content │ ├── LaraDocumentation.shfbproj │ └── icons │ │ └── Help.png ├── LaraUI.sln ├── LaraUI.sln.licenseheader ├── LaraUI │ ├── .gitignore │ ├── Assets │ │ └── Error.svg │ ├── Autocomplete │ │ ├── AutocompleteElement.cs │ │ ├── AutocompleteEntry.cs │ │ ├── AutocompleteOptions.cs │ │ ├── AutocompletePayload.cs │ │ ├── AutocompleteRegistry.cs │ │ ├── AutocompleteResponse.cs │ │ ├── AutocompleteService.cs │ │ └── IAutocompleteProvider.cs │ ├── Components │ │ ├── ComponentRegistry.cs │ │ ├── Fragment.cs │ │ ├── LaraWebComponent.cs │ │ ├── RenderIf.cs │ │ ├── Shadow.cs │ │ ├── Slot.cs │ │ ├── SlottedCalculator.cs │ │ ├── WebComponent.cs │ │ └── WebComponentOptions.cs │ ├── DOM │ │ ├── Attributes.cs │ │ ├── BlockOptions.cs │ │ ├── ChildrenBindingSubscription.cs │ │ ├── Document.cs │ │ ├── DocumentIdMap.cs │ │ ├── DocumentWriter.cs │ │ ├── DomSurgeon.cs │ │ ├── DuplicateElementIdException.cs │ │ ├── Element.cs │ │ ├── ElementFactory.cs │ │ ├── EventSettings.cs │ │ ├── GlobalSerializer.cs │ │ ├── HtmlReference.cs │ │ ├── MessageRegistry.cs │ │ ├── Node.cs │ │ ├── NodeExtensions.cs │ │ └── TextNode.cs │ ├── Delta │ │ ├── AttributeEditedDelta.cs │ │ ├── AttributeRemovedDelta.cs │ │ ├── BaseDelta.cs │ │ ├── ClearChildrenDelta.cs │ │ ├── ContentArrayNode.cs │ │ ├── ContentAttribute.cs │ │ ├── ContentElementNode.cs │ │ ├── ContentNode.cs │ │ ├── ContentPlaceholder.cs │ │ ├── ContentTextNode.cs │ │ ├── ElementValue.cs │ │ ├── EventResult.cs │ │ ├── FocusDelta.cs │ │ ├── NodeAddedDelta.cs │ │ ├── NodeInsertedDelta.cs │ │ ├── NodeLocator.cs │ │ ├── NodeRemovedDelta.cs │ │ ├── PlugOptions.cs │ │ ├── RemoveElementDelta.cs │ │ ├── RenderDelta.cs │ │ ├── ReplaceDelta.cs │ │ ├── ServerEventsDelta.cs │ │ ├── SetCheckedDelta.cs │ │ ├── SetIdDelta.cs │ │ ├── SetValueDelta.cs │ │ ├── SubmitJsDelta.cs │ │ ├── SubscribeDelta.cs │ │ ├── SwapChildrenDelta.cs │ │ ├── TextModifiedDelta.cs │ │ ├── UnRenderDelta.cs │ │ └── UnsubscribeDelta.cs │ ├── Elements │ │ ├── GenericElement.cs │ │ ├── HtmlAnchorElement.cs │ │ ├── HtmlBodyElement.cs │ │ ├── HtmlButtonElement.cs │ │ ├── HtmlColGroupElement.cs │ │ ├── HtmlDivElement.cs │ │ ├── HtmlHeadElement.cs │ │ ├── HtmlHeadingElement.cs │ │ ├── HtmlImageElement.cs │ │ ├── HtmlInputElement.cs │ │ ├── HtmlLabelElement.cs │ │ ├── HtmlLiElement.cs │ │ ├── HtmlLinkElement.cs │ │ ├── HtmlMetaElement.cs │ │ ├── HtmlMeterElement.cs │ │ ├── HtmlOlElement.cs │ │ ├── HtmlOptionElement.cs │ │ ├── HtmlOptionGroupElement.cs │ │ ├── HtmlParagraphElement.cs │ │ ├── HtmlScriptElement.cs │ │ ├── HtmlSelectElement.cs │ │ ├── HtmlSpanElement.cs │ │ ├── HtmlTableCellElement.cs │ │ ├── HtmlTableElement.cs │ │ ├── HtmlTableHeaderElement.cs │ │ ├── HtmlTableRowElement.cs │ │ ├── HtmlTableSectionElement.cs │ │ ├── HtmlTextAreaElement.cs │ │ └── HtmlTitleElement.cs │ ├── GlobalSuppressions.cs │ ├── Integrative.Lara.xml │ ├── LaraUI.csproj │ ├── LaraUI.csproj.DotSettings │ ├── Main │ │ ├── Application.cs │ │ ├── BaseContext.cs │ │ ├── BinaryServiceContent.cs │ │ ├── BinaryServicePublished.cs │ │ ├── Connection.cs │ │ ├── Connections.cs │ │ ├── GlobalConstants.cs │ │ ├── IBinaryService.cs │ │ ├── INavigation.cs │ │ ├── IPage.cs │ │ ├── IPageContext.cs │ │ ├── IPublishedItem.cs │ │ ├── IWebService.cs │ │ ├── IWebServiceContext.cs │ │ ├── JSBridge.cs │ │ ├── LaraBinaryServiceAttribute.cs │ │ ├── LaraPageAttribute.cs │ │ ├── LaraUI.cs │ │ ├── LaraWebServiceAttribute.cs │ │ ├── Navigation.cs │ │ ├── PageContext.cs │ │ ├── PagePublished.cs │ │ ├── Published.cs │ │ ├── Session.cs │ │ ├── SessionStorage.cs │ │ ├── SingleElementPage.cs │ │ ├── StaleConnectionsCollector.cs │ │ ├── StaticContent.cs │ │ ├── TemplateBuilder.cs │ │ ├── WebServiceContent.cs │ │ ├── WebServiceContext.cs │ │ └── WebServicePublished.cs │ ├── Middleware │ │ ├── BaseHandler.cs │ │ ├── BrowserAppController.cs │ │ ├── ClientEventMessage.cs │ │ ├── ClientLibraryHandler.cs │ │ ├── ContentTypes.cs │ │ ├── DefaultErrorPage.cs │ │ ├── DiscardHandler.cs │ │ ├── DiscardParameters.cs │ │ ├── ErrorPages.cs │ │ ├── EventParameters.cs │ │ ├── FormFile.cs │ │ ├── FormFileCollection.cs │ │ ├── IModeController.cs │ │ ├── KeepAliveHandler.cs │ │ ├── LaraMiddleware.cs │ │ ├── LocalhostFilter.cs │ │ ├── MiddlewareCommon.cs │ │ ├── NotFoundMiddleware.cs │ │ ├── PostEventContext.cs │ │ ├── PostEventHandler.cs │ │ ├── PublishedItemHandler.cs │ │ ├── Sequencer.cs │ │ ├── ServerEvent.cs │ │ ├── ServerEventsController.cs │ │ ├── StatusCodeException.cs │ │ └── StatusForbiddenException.cs │ ├── NewVersionChecklist.txt │ ├── Reactive │ │ ├── BindableBase.cs │ │ ├── BindingExtensions.cs │ │ ├── BindingOptions.cs │ │ ├── BindingSubscription.cs │ │ ├── CollectionUpdater.cs │ │ └── ObsoleteElement.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Tools │ │ ├── ApplicationBuilderLaraExtensions.cs │ │ ├── AssembliesReader.cs │ │ ├── AsyncEvent.cs │ │ ├── ClassEditor.cs │ │ ├── DocumentLocal.cs │ │ ├── LaraBuilder.cs │ │ ├── LaraJson.cs │ │ ├── LaraOptions.cs │ │ ├── LaraTools.cs │ │ ├── NoCurrentSessionException.cs │ │ ├── SemaphoreSlimExtensions.cs │ │ ├── ServerLauncher.cs │ │ └── SessionLocal.cs │ ├── docfx.json │ └── pack.bat ├── SampleProject │ ├── Assets │ │ └── Coffee.svg │ ├── Common │ │ ├── CountryList.cs │ │ ├── CountrySelector.cs │ │ ├── SampleAppBootstrap.cs │ │ └── Tools.cs │ ├── Components │ │ ├── CheckboxSample.cs │ │ ├── CounterSample.cs │ │ ├── KitchenSinkComponent.cs │ │ ├── LockingSample.cs │ │ ├── LongRunningSample.cs │ │ ├── MultiselectSample.cs │ │ ├── SelectSample.cs │ │ ├── UploadSample.cs │ │ └── WeekdayCombo.cs │ ├── LaraSample.csproj │ ├── Main │ │ └── Program.cs │ ├── Pages │ │ ├── KitchenSinkPage.cs │ │ ├── ServerEventsPage.cs │ │ └── UploadFilePage.cs │ ├── Properties │ │ └── launchSettings.json │ └── SampleProject.csproj └── Tests │ ├── Assets │ ├── Compressible.bmp │ └── pexels-photo-248673.jpeg │ ├── Components │ ├── AutocompleteTesting.cs │ └── ComponentTesting.cs │ ├── DOM │ ├── AttributesTesting.cs │ ├── BindingsTesting.cs │ ├── BuilderTesting.cs │ ├── ClassEditorTesting.cs │ ├── DomOperationsTesting.cs │ ├── ElementAttributes.cs │ ├── EventsTesting.cs │ ├── GlobalAttributesTesting.cs │ └── LaraBuilderTesting.cs │ ├── Delta │ ├── AttributeEditTesting.cs │ ├── DeltaTesting.cs │ └── LocatorTesting.cs │ ├── Main │ ├── ButtonCounterPage.cs │ ├── ConnectionTesting.cs │ ├── ConnectionsTesting.cs │ ├── MyPage.cs │ ├── PublishedTesting.cs │ ├── StaleTesting.cs │ └── StaticContentTesting.cs │ ├── Middleware │ ├── DummyContext.cs │ ├── DummyContextTesting.cs │ ├── ErrorPagesTesting.cs │ ├── EventParametersTesting.cs │ ├── MiddlewareTesting.cs │ ├── ServerEventsTesting.cs │ ├── ToolsTesting.cs │ └── WebServicesTesting.cs │ └── Tests.csproj └── support └── jetbrains.svg /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: src/LaraUI.sln 3 | mono: none 4 | dotnet: 5.0.100 5 | 6 | before_install: 7 | - sudo apt-get update 8 | - sudo apt-get install nuget dotnet-sdk-2.1=2.1.300-1 9 | - curl -s -o $HOME/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/v0.31.0/nvm.sh 10 | - source $HOME/.nvm/nvm.sh 11 | - nvm install stable 12 | - npm -v 13 | - node -v 14 | - dotnet tool install coveralls.net --tool-path tools 15 | 16 | install: 17 | - nuget restore src/LaraUI.sln 18 | 19 | script: 20 | - cd src 21 | - echo "Debug" > environment.txt 22 | - cd LaraClient 23 | - npm install . 24 | - npm run-script build 25 | - cd .. 26 | - cd .. 27 | - dotnet build --configuration Debug src/LaraUI/LaraUI.csproj 28 | - dotnet build --configuration Debug --framework net5.0 src/Tests/Tests.csproj 29 | - dotnet test --configuration Debug --framework net5.0 src/Tests/Tests.csproj /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=results.xml 30 | 31 | after_script: 32 | - REPO_COMMIT_AUTHOR=$(git show -s --pretty=format:"%cn") 33 | - REPO_COMMIT_AUTHOR_EMAIL=$(git show -s --pretty=format:"%ce") 34 | - REPO_COMMIT_MESSAGE=$(git show -s --pretty=format:"%s") 35 | - echo $TRAVIS_COMMIT 36 | - echo $TRAVIS_BRANCH 37 | - echo $REPO_COMMIT_AUTHOR 38 | - echo $REPO_COMMIT_AUTHOR_EMAIL 39 | - echo $REPO_COMMIT_MESSAGE 40 | - echo $TRAVIS_JOB_ID 41 | - ./tools/csmacnz.Coveralls --opencover -i src/Tests/results.xml --repoToken $COVERALLS_TOKEN --commitId $TRAVIS_COMMIT --commitBranch $TRAVIS_BRANCH --commitAuthor "$REPO_COMMIT_AUTHOR" --commitEmail "$REPO_COMMIT_AUTHOR_EMAIL" --commitMessage "$REPO_COMMIT_MESSAGE" --jobId $TRAVIS_JOB_ID --serviceName travis-ci --useRelativePaths 42 | -------------------------------------------------------------------------------- /Assets/Integrative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/integrativesoft/lara/67fb43a3e0d1b5f7964e46b9552ad909dcf9de7a/Assets/Integrative.png -------------------------------------------------------------------------------- /src/Boilerplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace SampleApp 6 | { 7 | public static class Program 8 | { 9 | public static async Task Main() 10 | { 11 | // create and start application 12 | const int port = 8182; 13 | using var app = new Application(); 14 | app.PublishPage("/", () => new HttpContextExample()); 15 | await app.Start(new StartServerOptions { Port = port }); 16 | 17 | // print address on console (set project's output type to WinExe to avoid console) 18 | var address = $"http://localhost:{port}"; 19 | Console.WriteLine($"Listening on {address}/"); 20 | 21 | // helper function to launch browser (comment out as needed) 22 | LaraUI.LaunchBrowser(address); 23 | 24 | // wait for ASP.NET Core shutdown 25 | await app.WaitForShutdown(); 26 | } 27 | } 28 | 29 | internal class MyCounterComponent : WebComponent 30 | { 31 | private int _value; // triggers PropertyChanged event 32 | public int Value { get => _value; set => SetProperty(ref _value, value); } 33 | 34 | public MyCounterComponent() 35 | { 36 | ShadowRoot.Children = new Node[] 37 | { 38 | new HtmlDivElement() // on PropertyChanged, assigns InnerText 39 | .Bind(this, x => x.InnerText = Value.ToString()), 40 | new HtmlButtonElement 41 | { InnerText = "Increase" } 42 | .Event("click", () => Value++) 43 | }; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/ComponentPropertyExample.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class ComponentPropertyExample : WebComponent 4 | { 5 | public ComponentPropertyExample() 6 | { 7 | var myLabel = new HtmlSpanElement 8 | { 9 | InnerText = "Hello!" 10 | }; 11 | ShadowRoot.Children = new Element[] 12 | { 13 | new MyLabelComponent 14 | { 15 | Label = myLabel 16 | }, 17 | }; 18 | } 19 | } 20 | 21 | internal class MyLabelComponent : WebComponent 22 | { 23 | private Element _label; 24 | public Element Label { get => _label; set => SetProperty(ref _label, value); } 25 | 26 | public MyLabelComponent() 27 | { 28 | ShadowRoot.Children = new Element[] 29 | { 30 | new RenderIf(this, () => _label != null, () => _label) 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/ComposingComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class ComposingComponent : WebComponent 4 | { 5 | public ComposingComponent() // parent component 6 | { 7 | ShadowRoot.Children = new Element[] 8 | { 9 | new ItemComponent { Name = "Sara" }, 10 | new ItemComponent { Name = "Mike" }, 11 | new ItemComponent { Name = "Tom" }, 12 | }; 13 | } 14 | } 15 | 16 | internal class ItemComponent : WebComponent // child component 17 | { 18 | private string _name; 19 | public string Name { get => _name; set => SetProperty(ref _name, value); } 20 | 21 | public ItemComponent() 22 | { 23 | ShadowRoot.Children = new Element[] 24 | { 25 | new HtmlDivElement 26 | { 27 | Children = new Element[] 28 | { 29 | new HtmlTableCellElement() 30 | .Bind(this, x => x.InnerText = Name), 31 | } 32 | } 33 | }; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/ConditionalRenderComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class ConditionalRenderComponent : WebComponent 4 | { 5 | private bool _showText; 6 | public bool ShowText { get => _showText; set => SetProperty(ref _showText, value); } 7 | 8 | public ConditionalRenderComponent() 9 | { 10 | ShadowRoot.Children = new Node[] 11 | { 12 | new HtmlDivElement 13 | { 14 | InnerText = "Hello!", 15 | } 16 | .Bind(this, x => x.Render = ShowText) // Render property here 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/DocumentContextExample.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class DocumentContextExample : WebComponent 4 | { 5 | const string IconId = "MyIconElement"; 6 | 7 | public DocumentContextExample() 8 | { 9 | ShadowRoot.Children = new Element[] 10 | { 11 | new HtmlDivElement 12 | { 13 | InnerText = "Check the title and icon of this webpage" 14 | } 15 | }; 16 | } 17 | 18 | protected override void OnConnect() // our component is placed on Document 19 | { 20 | base.OnConnect(); 21 | var icon = Document.GetElementById(IconId); 22 | if (icon != null) return; 23 | Document.Head.AppendChild(new HtmlLinkElement 24 | { 25 | Id = IconId, 26 | Rel = "icon", 27 | HRef = "https://stackoverflow.com/favicon.ico", 28 | }); 29 | Document.Head.AppendChild(new HtmlTitleElement 30 | { 31 | InnerText = "Hello title", 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/HttpContextExample.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class HttpContextExample : WebComponent 4 | { 5 | private string _message; 6 | public string Message { get => _message; set => SetProperty(ref _message, value); } 7 | 8 | public HttpContextExample() 9 | { 10 | ShadowRoot.Children = new Element[] 11 | { 12 | new HtmlDivElement() 13 | .Bind(this, x => x.InnerText = Message) 14 | }; 15 | } 16 | 17 | protected override void OnConnect() 18 | { 19 | base.OnConnect(); 20 | Message = $"Your IP is {LaraUI.Context.Http.Connection.RemoteIpAddress}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/LoopComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | using System.Collections.ObjectModel; 3 | 4 | internal class MyList : WebComponent 5 | { 6 | private readonly ObservableCollection _names = new ObservableCollection(); 7 | 8 | public MyList() 9 | { 10 | ShadowRoot.Children = new Node[] 11 | { 12 | Fragment.ForEach(_names, (string name) => new HtmlDivElement { InnerText = name }), 13 | }; 14 | _names.Add("Sarah"); 15 | _names.Add("John"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/RedBoxExample.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class RedBoxExample : WebComponent 4 | { 5 | public RedBoxExample() 6 | { 7 | ShadowRoot.Children = new Element[] 8 | { 9 | new RedBoxComponent 10 | { 11 | Children = new Element[] 12 | { 13 | new HtmlDivElement 14 | { 15 | InnerText = "Hello!", 16 | } 17 | } 18 | } 19 | }; 20 | } 21 | } 22 | 23 | internal class RedBoxComponent : WebComponent 24 | { 25 | public RedBoxComponent() 26 | { 27 | ShadowRoot.Children = new Element[] 28 | { 29 | new HtmlDivElement 30 | { 31 | Style = "border: solid 3px red", 32 | Children = new Element[] 33 | { 34 | new Slot(), 35 | } 36 | } 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/SimpleComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class SimpleComponent : WebComponent 4 | { 5 | private string _message; 6 | private string Message { get => _message; set => SetProperty(ref _message, value); } 7 | 8 | public SimpleComponent() 9 | { 10 | Message = "My Lara app"; 11 | ShadowRoot.Children = new Node[] 12 | { 13 | new HtmlDivElement 14 | { 15 | Children = new Node[] 16 | { 17 | new HtmlSpanElement() 18 | .Bind(this, x => x.InnerText = Message) 19 | } 20 | } 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/UserInputComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class UserInputComponent : WebComponent 4 | { 5 | int _counter; 6 | public int Counter { get => _counter; set => SetProperty(ref _counter, value); } 7 | 8 | public UserInputComponent() 9 | { 10 | ShadowRoot.Children = new Element[] 11 | { 12 | new HtmlDivElement() 13 | .Bind(this, x => x.InnerText = Counter.ToString()), 14 | new HtmlButtonElement 15 | { InnerText = "Increase" } 16 | .Event("click", () => Counter++) 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Boilerplate/Wiki/UserTextComponent.cs: -------------------------------------------------------------------------------- 1 | using Integrative.Lara; 2 | 3 | internal class UserTextComponent : WebComponent 4 | { 5 | private string _name; 6 | public string Name { get => _name; set => SetProperty(ref _name, value); } 7 | 8 | public UserTextComponent() 9 | { 10 | Name = "Taylor"; 11 | ShadowRoot.Children = new Element[] 12 | { 13 | new HtmlDivElement 14 | { InnerText = "Please enter your name: " }, 15 | new HtmlInputElement() 16 | .Bind(this, x => x.Value = Name) // if property changes, update element 17 | .BindBack(x => Name = x.Value), // if element changes, update property 18 | new HtmlButtonElement 19 | { InnerText = "Read" } 20 | .Event("click", () => { }), 21 | new HtmlDivElement() 22 | .Bind(this, x => x.InnerText = $"Your name is {Name}"), 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Boilerplate/WikiExamples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | WikiExamples 7 | SampleApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/LaraClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29519.181 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "LaraClient", "LaraClient\LaraClient.njsproj", "{6F0BEE5A-5C72-4DCB-9173-BD17BA95A76F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6F0BEE5A-5C72-4DCB-9173-BD17BA95A76F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6F0BEE5A-5C72-4DCB-9173-BD17BA95A76F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6F0BEE5A-5C72-4DCB-9173-BD17BA95A76F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6F0BEE5A-5C72-4DCB-9173-BD17BA95A76F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3770CCA5-4282-46C3-9C5D-65FC0534FF93} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/LaraClient/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .js 4 | -------------------------------------------------------------------------------- /src/LaraClient/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "rules": { 15 | "no-console": 1, 16 | "prettier/prettier": 2, 17 | "no-unused-vars": [2, {"args": "all", "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/LaraClient/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "none", 4 | "singleQuote": false, 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /src/LaraClient/README.md: -------------------------------------------------------------------------------- 1 | # LaraClient 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/LaraClient/dist/lara-client.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Lara Web Engine 3 | * Copyright (c) 2019-2020 Integrative Software LLC. 4 | * License: Apache-2.0 5 | */ 6 | 7 | /*! 8 | * Sizzle CSS Selector Engine v2.3.5 9 | * https://sizzlejs.com/ 10 | * 11 | * Copyright JS Foundation and other contributors 12 | * Released under the MIT license 13 | * https://js.foundation/ 14 | * 15 | * Date: 2020-03-14 16 | */ 17 | 18 | /*! 19 | * jQuery UI Autocomplete 1.12.1 20 | * http://jqueryui.com 21 | * 22 | * Copyright jQuery Foundation and other contributors 23 | * Released under the MIT license. 24 | * http://jquery.org/license 25 | */ 26 | 27 | /*! 28 | * jQuery UI Keycode 1.12.1 29 | * http://jqueryui.com 30 | * 31 | * Copyright jQuery Foundation and other contributors 32 | * Released under the MIT license. 33 | * http://jquery.org/license 34 | */ 35 | 36 | /*! 37 | * jQuery UI Menu 1.12.1 38 | * http://jqueryui.com 39 | * 40 | * Copyright jQuery Foundation and other contributors 41 | * Released under the MIT license. 42 | * http://jquery.org/license 43 | */ 44 | 45 | /*! 46 | * jQuery UI Position 1.12.1 47 | * http://jqueryui.com 48 | * 49 | * Copyright jQuery Foundation and other contributors 50 | * Released under the MIT license. 51 | * http://jquery.org/license 52 | * 53 | * http://api.jqueryui.com/position/ 54 | */ 55 | 56 | /*! 57 | * jQuery UI Unique ID 1.12.1 58 | * http://jqueryui.com 59 | * 60 | * Copyright jQuery Foundation and other contributors 61 | * Released under the MIT license. 62 | * http://jquery.org/license 63 | */ 64 | 65 | /*! 66 | * jQuery UI Widget 1.12.1 67 | * http://jqueryui.com 68 | * 69 | * Copyright jQuery Foundation and other contributors 70 | * Released under the MIT license. 71 | * http://jquery.org/license 72 | */ 73 | -------------------------------------------------------------------------------- /src/LaraClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lara-client", 3 | "version": "0.5.8", 4 | "description": "LaraClient", 5 | "module": "src/index.ts", 6 | "private": true, 7 | "author": { 8 | "name": "Integrative Software LLC" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "build": "webpack --env debug=1", 13 | "release": "webpack --env release=1", 14 | "clean": "echo \"Webpack clean not needed\" && exit 0", 15 | "lint": "eslint . --ext .ts", 16 | "prettier-format": "prettier --config .prettierrc 'src/*.ts' --write" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^16.11.8", 20 | "@typescript-eslint/eslint-plugin": "^5.4.0", 21 | "@typescript-eslint/parser": "^5.4.0", 22 | "clean-webpack-plugin": "^4.0.0", 23 | "eslint": "^8.2.0", 24 | "eslint-config-prettier": "^8.3.0", 25 | "eslint-plugin-prettier": "^4.0.0", 26 | "npm-check-updates": "^16.0.5", 27 | "prettier": "^2.4.1", 28 | "terser-webpack-plugin": "5.2.5", 29 | "ts-loader": "^9.2.6", 30 | "typescript": "^4.5.2", 31 | "webpack": "^5.76.0", 32 | "webpack-cli": "^4.9.1" 33 | }, 34 | "dependencies": { 35 | "@types/debounce": "^1.2.1", 36 | "@types/jquery": "^3.5.8", 37 | "@types/jquery.blockui": "0.0.29", 38 | "@types/jqueryui": "^1.12.16", 39 | "debounce": "^1.2.1", 40 | "webpack-jquery-ui": "^2.0.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/LaraClient/src/Blocker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2020 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | import { PlugOptions } from "./index" 8 | import "./blockUI.js" 9 | 10 | export function block(plug: PlugOptions): void { 11 | if (plug.Block) { 12 | const target = resolveTarget(plug) 13 | const params = buildParameters(plug) 14 | if (target) { 15 | $(target).block(params) 16 | } else { 17 | $.blockUI(params) 18 | } 19 | } 20 | } 21 | 22 | export function unblock(plug: PlugOptions): void { 23 | if (plug.Block) { 24 | const target = resolveTarget(plug) 25 | if (target) { 26 | $(target).unblock() 27 | } else { 28 | $.unblockUI() 29 | } 30 | } 31 | } 32 | 33 | function buildParameters(plug: PlugOptions): JQBlockUIOptions { 34 | const result: JQBlockUIOptions = {} 35 | const shownId = plug.BlockShownId 36 | if (shownId) { 37 | setElementCSS(result) 38 | } else { 39 | setDefaultCSS(result) 40 | } 41 | if (shownId) { 42 | result.message = $("#" + shownId) 43 | } else if (plug.BlockHTML) { 44 | result.message = plug.BlockHTML 45 | } else { 46 | result.message = null 47 | } 48 | result.baseZ = 2000 49 | return result 50 | } 51 | 52 | function resolveTarget(plug: PlugOptions): Element { 53 | if (plug.BlockElementId) { 54 | const el = document.getElementById(plug.BlockElementId) 55 | if (el) { 56 | return el 57 | } 58 | } 59 | return null 60 | } 61 | 62 | function setElementCSS(options: JQBlockUIOptions): void { 63 | options.css = { 64 | position: "absolute", 65 | top: "50%", 66 | left: "50%", 67 | transform: "translate(-50%, -50%)", 68 | padding: "unset", 69 | margin: "unset", 70 | border: "unset", 71 | width: "unset", 72 | "text-align": "unset", 73 | color: "unset", 74 | "background-color": "unset" 75 | } 76 | } 77 | 78 | function setDefaultCSS(options: JQBlockUIOptions): void { 79 | options.css = { 80 | border: "none", 81 | padding: "15px", 82 | backgroundColor: "#000", 83 | "-webkit-border-radius": "10px", 84 | "-moz-border-radius": "10px", 85 | "border-radius": "10px", 86 | opacity: ".5", 87 | color: "#fff", 88 | fontSize: "18px", 89 | fontFamily: "Verdana,Arial", 90 | fontWeight: 200 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/LaraClient/src/ContentInterfaces.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2020 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | export enum ContentNodeType { 8 | // eslint-disable-next-line no-unused-vars 9 | Element = 1, 10 | // eslint-disable-next-line no-unused-vars 11 | Text = 2, 12 | // eslint-disable-next-line no-unused-vars 13 | Array = 3, 14 | // eslint-disable-next-line no-unused-vars 15 | Placeholder = 4 16 | } 17 | 18 | export interface ContentNode { 19 | Type: ContentNodeType 20 | } 21 | 22 | export interface ContentTextNode extends ContentNode { 23 | Data: string 24 | } 25 | 26 | export interface ContentAttribute { 27 | Attribute: string 28 | Value: string 29 | } 30 | 31 | export interface ContentElementNode extends ContentNode { 32 | TagName: string 33 | NS: string 34 | Attributes: ContentAttribute[] 35 | Children: ContentNode[] 36 | } 37 | 38 | export interface ContentArrayNode extends ContentNode { 39 | Nodes: ContentNode[] 40 | } 41 | 42 | export interface ContentPlaceholder extends ContentNode { 43 | ElementId: string 44 | } 45 | -------------------------------------------------------------------------------- /src/LaraClient/src/Initializer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2020 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | export function clean(node: Node): void { 8 | for (let n = 0; n < node.childNodes.length; n++) { 9 | const child = node.childNodes[n] 10 | if ( 11 | child.nodeType === 8 || 12 | (child.nodeType === 3 && !/\S/.test(child.nodeValue)) 13 | ) { 14 | node.removeChild(child) 15 | n-- 16 | } else if (child.nodeType === 1) { 17 | clean(child) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LaraClient/src/Sequencer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2020 Integrative Software LLC 3 | Created: 10/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | type resolver = (_value?: boolean | PromiseLike) => void 8 | 9 | export class Sequencer { 10 | private next: number 11 | private pending: Map 12 | 13 | constructor() { 14 | this.next = 1 15 | this.pending = new Map() 16 | } 17 | 18 | async waitForTurn(turn: number): Promise { 19 | if (turn == 0) { 20 | return true 21 | } else if (turn == this.next) { 22 | this.next++ 23 | this.flushPending() 24 | return true 25 | } else if (turn > this.next) { 26 | let resolver: resolver 27 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 28 | const task = new Promise((resolve, _reject) => { 29 | resolver = resolve 30 | }) 31 | this.pending.set(turn, resolver) 32 | return task 33 | } else { 34 | return false 35 | } 36 | } 37 | 38 | private flushPending(): void { 39 | while (this.pending.has(this.next)) { 40 | const resolver = this.pending.get(this.next) 41 | this.pending.delete(this.next) 42 | resolver() 43 | this.next++ 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/LaraClient/src/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | const content: any 4 | export default content 5 | } 6 | -------------------------------------------------------------------------------- /src/LaraClient/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "lib": [ "es6", "dom" ], 8 | "sourceMap": true, 9 | "allowJs": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/LaraClient/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | 5 | const banner = 'Lara Web Engine\n' 6 | + 'Copyright (c) 2019-2020 Integrative Software LLC.\n' 7 | + 'License: Apache-2.0'; 8 | 9 | module.exports = env => { 10 | 11 | var buildMode, devtool; 12 | if (env.release) { 13 | buildMode = "production"; 14 | devtool = "nosources-source-map"; 15 | } else { 16 | buildMode = "development"; 17 | devtool = "source-map"; 18 | } 19 | console.log("mode: " + buildMode); 20 | 21 | return { 22 | entry: './src/index.ts', 23 | mode: buildMode, 24 | devtool: devtool, 25 | plugins: [ 26 | new CleanWebpackPlugin(), 27 | new webpack.BannerPlugin(banner), 28 | new webpack.ProvidePlugin({ 29 | $: "jquery", 30 | jQuery: "jquery" 31 | }) 32 | ], 33 | module: { 34 | rules: [ 35 | { 36 | use: 'ts-loader', 37 | exclude: /node_modules/ 38 | }, 39 | { 40 | test: /\.css$/, 41 | use: ["style-loader", "css-loader"] 42 | }, 43 | { 44 | test: /\.(jpe?g|png|gif)$/i, 45 | loader: "file-loader", 46 | options: { 47 | name: '[name].[ext]', 48 | outputPath: 'assets/images/' 49 | } 50 | } 51 | ] 52 | }, 53 | resolve: { 54 | extensions: ['.ts', '.js'], 55 | alias: { 56 | jquery: "jquery/src/jquery" 57 | } 58 | }, 59 | output: { 60 | filename: 'lara-client.js', 61 | path: path.resolve(__dirname, 'dist'), 62 | libraryTarget: 'var', 63 | library: 'LaraUI' 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/LaraDocumentation/Content/Welcome.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Use the menu on the left to navigate. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/LaraDocumentation/ContentLayout.content: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/LaraDocumentation/icons/Help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/integrativesoft/lara/67fb43a3e0d1b5f7964e46b9552ad909dcf9de7a/src/LaraDocumentation/icons/Help.png -------------------------------------------------------------------------------- /src/LaraUI.sln.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: designer.cs generated.cs 2 | extensions: .cs .cpp .h .js .ts 3 | /* 4 | Copyright (c) %CurrentYear% Integrative Software LLC 5 | Created: %CreationMonth%/%CreationYear% 6 | Author: Pablo Carbonell 7 | */ 8 | 9 | extensions: .aspx .ascx 10 | <%-- 11 | Copyright (c) %CurrentYear% Integrative Software LLC 12 | Created: %CreationMonth%/%CreationYear% 13 | Author: Pablo Carbonell 14 | --%> 15 | extensions: .vb 16 | 'Copyright (c) %CurrentYear% Integrative Software LLC 17 | 'Created: %CreationMonth%/%CreationYear% 18 | 'Author: Pablo Carbonell 19 | extensions: .xml .config .xsd 20 | -------------------------------------------------------------------------------- /src/LaraUI/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | -------------------------------------------------------------------------------- /src/LaraUI/Assets/Error.svg: -------------------------------------------------------------------------------- 1 | Asset 140 -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompleteEntry.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Default implementation for IAutocompleteEntry 13 | /// 14 | [DataContract] 15 | public class AutocompleteEntry 16 | { 17 | /// 18 | /// Optional custom HTML for the autocomplete row shown 19 | /// 20 | /// 21 | [DataMember(Name = "html")] 22 | public string? Html { get; set; } 23 | 24 | /// 25 | /// Text to fill the input control when selected 26 | /// 27 | [DataMember(Name = "label")] 28 | public string? Label { get; set; } 29 | 30 | /// 31 | /// Value property associated with the entry 32 | /// 33 | [DataMember(Name = "code")] 34 | public string? Code { get; set; } 35 | 36 | /// 37 | /// Subtitle to show when using default HTML 38 | /// 39 | [DataMember(Name = "subtitle")] 40 | public string? Subtitle { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompleteOptions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// Autocomplete options 11 | /// 12 | public class AutocompleteOptions 13 | { 14 | /// 15 | /// Minimum number of characters required to trigger autocomplete suggestions 16 | /// 17 | public int MinLength { get; set; } 18 | 19 | /// 20 | /// Automatically focus on selection list 21 | /// 22 | public bool AutoFocus { get; set; } = true; 23 | 24 | /// 25 | /// When true, only the suggested values can be selected 26 | /// 27 | public bool Strict { get; set; } 28 | 29 | /// 30 | /// Autocomplete suggestions provider 31 | /// 32 | public IAutocompleteProvider? Provider { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompletePayload.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal class AutocompletePayload 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public bool AutoFocus { get; set; } 19 | 20 | [DataMember] 21 | public int MinLength { get; set; } 22 | 23 | [DataMember] 24 | public bool Strict { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompleteRegistry.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal class AutocompleteRegistry 13 | { 14 | private readonly SessionLocal> _map; 15 | private readonly object _mapLock = new object(); 16 | 17 | public AutocompleteRegistry() 18 | { 19 | _map = new SessionLocal>(); 20 | } 21 | 22 | public bool TryGet(string key, [NotNullWhen(true)] out AutocompleteElement? element) 23 | { 24 | element = default; 25 | lock (_mapLock) 26 | { 27 | var map = _map.Value; 28 | return map != null 29 | && map.TryGetValue(key, out element); 30 | } 31 | } 32 | 33 | public void Set(string key, AutocompleteElement element) 34 | { 35 | lock (_mapLock) 36 | { 37 | var map = _map.Value; 38 | if (map == null) 39 | { 40 | map = new Dictionary(); 41 | _map.Value = map; 42 | map.Add(key, element); 43 | } 44 | else 45 | { 46 | map.Remove(key); 47 | map.Add(key, element); 48 | } 49 | } 50 | } 51 | 52 | public void Remove(string key) 53 | { 54 | lock (_mapLock) 55 | { 56 | _map.Value?.Remove(key); 57 | } 58 | } 59 | 60 | public int Count => GetCount(); 61 | 62 | private int GetCount() 63 | { 64 | var value = GetValue(); 65 | return value?.Count ?? 0; 66 | } 67 | 68 | private Dictionary? GetValue() 69 | { 70 | lock (_mapLock) 71 | { 72 | return _map.Value; 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompleteResponse.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Autocomplete results 14 | /// 15 | [DataContract] 16 | public class AutocompleteResponse 17 | { 18 | /// 19 | /// List of autocomplete entries 20 | /// 21 | [DataMember] 22 | public List? Suggestions { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/AutocompleteService.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal class AutocompleteRequest 14 | { 15 | [DataMember] 16 | public string Key { get; set; } = string.Empty; 17 | 18 | [DataMember] 19 | public string Term { get; set; } = string.Empty; 20 | } 21 | 22 | internal class AutocompleteService : IWebService 23 | { 24 | public const string Address = "/lara_autocomplete"; 25 | 26 | private static readonly AutocompleteRegistry _Map = new AutocompleteRegistry(); 27 | 28 | public Task Execute() 29 | { 30 | return Execute(LaraUI.Service.RequestBody); 31 | } 32 | 33 | internal static async Task Execute(string json) 34 | { 35 | var request = LaraUI.JSON.Parse(json); 36 | if (!_Map.TryGet(request.Key, out var element)) 37 | { 38 | return string.Empty; 39 | } 40 | var options = element.GetOptions(); 41 | if (options?.Provider == null) 42 | { 43 | return string.Empty; 44 | } 45 | 46 | var response = await options.Provider.GetAutocompleteList(request.Term); 47 | return LaraUI.JSON.Stringify(response); 48 | } 49 | 50 | public static void Register(string key, AutocompleteElement element) 51 | { 52 | _Map.Set(key, element); 53 | } 54 | 55 | public static void Unregister(string key) 56 | { 57 | _Map.Remove(key); 58 | } 59 | 60 | public static int RegisteredCount => _Map.Count; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/LaraUI/Autocomplete/IAutocompleteProvider.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Interface for a class that provides autocomplete suggestions 13 | /// 14 | public interface IAutocompleteProvider 15 | { 16 | /// 17 | /// Method that provides autocomplete suggestions 18 | /// 19 | /// Search term typed 20 | /// Autocomplete response 21 | Task GetAutocompleteList(string term); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/LaraUI/Components/ComponentRegistry.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal sealed class ComponentRegistry 13 | { 14 | private readonly Dictionary _components; 15 | 16 | public ComponentRegistry() 17 | { 18 | _components = new Dictionary(); 19 | } 20 | 21 | public void Register(string name, Type type) 22 | { 23 | name = name ?? throw new ArgumentNullException(nameof(name)); 24 | type = type ?? throw new ArgumentNullException(nameof(type)); 25 | if (!IsValidTagName(name)) 26 | { 27 | throw new ArgumentException(Resources.DashRequired); 28 | } 29 | 30 | if (!type.IsSubclassOf(typeof(WebComponent))) 31 | { 32 | throw new InvalidOperationException(Resources.MustInherit); 33 | } 34 | if (_components.TryGetValue(name, out var previous)) 35 | { 36 | var message = $"Duplicate entries for tag '{name}'. The class '{previous.FullName}' already registers the tag name."; 37 | throw new InvalidOperationException(message); 38 | } 39 | _components.Add(name, type); 40 | } 41 | 42 | public void Unregister(string tagName) 43 | { 44 | _components.Remove(tagName); 45 | } 46 | 47 | private static bool IsValidTagName(string tagName) 48 | { 49 | return !string.IsNullOrEmpty(tagName) 50 | && !tagName.Contains(" ", StringComparison.InvariantCulture) 51 | && tagName.Contains("-", StringComparison.InvariantCulture); 52 | } 53 | 54 | public bool TryGetComponent(string name, out Type type) 55 | { 56 | return _components.TryGetValue(name, out type); 57 | } 58 | 59 | public void Clear() 60 | { 61 | _components.Clear(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/LaraUI/Components/Fragment.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 1/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Collections.ObjectModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Fragment component to group element in a single parent 14 | /// 15 | public class Fragment : WebComponent 16 | { 17 | /// 18 | /// Constructor 19 | /// 20 | public Fragment() 21 | { 22 | ShadowRoot.AppendChild(new Slot()); 23 | } 24 | 25 | /// 26 | /// Creates a Fragment with a list of children that follows an observable collection 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | public static Fragment ForEach(ObservableCollection source, Func factory) 33 | { 34 | var fragment = new Fragment(); 35 | fragment.BindChildren(source, factory); 36 | return fragment; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LaraUI/Components/LaraWebComponent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Classes marked as LaraWebComponent will registered when executing LaraUI.PublishAssemblies() 13 | /// 14 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 15 | public sealed class LaraWebComponentAttribute : Attribute 16 | { 17 | /// 18 | /// Custom tag name for the component. Must contain the '-' character. 19 | /// 20 | public string ComponentTagName { get; } 21 | 22 | /// 23 | /// Constructor with custom tag name 24 | /// 25 | /// Tag name of the web component 26 | public LaraWebComponentAttribute(string customTagName) 27 | { 28 | ComponentTagName = customTagName; 29 | } 30 | 31 | /// 32 | /// Constructor 33 | /// 34 | public LaraWebComponentAttribute() : this("") 35 | { 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LaraUI/Components/RenderIf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace Integrative.Lara 5 | { 6 | /// 7 | /// Conditional render placeholder 8 | /// 9 | public class RenderIf : WebComponent 10 | { 11 | private readonly Func _criteria; 12 | private readonly Func _factory; 13 | private bool _rendered; 14 | 15 | /// 16 | /// Constructor 17 | /// 18 | /// 19 | /// 20 | /// 21 | public RenderIf(INotifyPropertyChanged source, Func criteria, Func factory) 22 | { 23 | _criteria = criteria; 24 | _factory = factory; 25 | source.PropertyChanged += (_, _) => Update(); 26 | } 27 | 28 | private void Update() 29 | { 30 | var rendered = _criteria(); 31 | if (rendered == _rendered) return; 32 | _rendered = rendered; 33 | if (rendered) 34 | { 35 | ShadowRoot.AppendChild(_factory()); 36 | } 37 | else 38 | { 39 | ShadowRoot.ClearChildren(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LaraUI/Components/Shadow.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal sealed class Shadow : Element 13 | { 14 | private const string ShadowTagName = "__shadow"; 15 | 16 | public Shadow(WebComponent parent) : base(ShadowTagName) 17 | { 18 | ParentComponent = parent; 19 | } 20 | 21 | public WebComponent ParentComponent { get; } 22 | 23 | internal override IEnumerable GetLightSlotted() 24 | { 25 | return Enumerable.Empty(); 26 | } 27 | 28 | internal override bool IsPrintable => false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LaraUI/Components/Slot.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Linq; 10 | 11 | namespace Integrative.Lara 12 | { 13 | /// 14 | /// A slot element is a placeholder inside a web component that you can fill with your own element 15 | /// 16 | public sealed class Slot : Element 17 | { 18 | /// 19 | /// The slot's name 20 | /// 21 | public string? Name 22 | { 23 | get => GetAttribute("name"); 24 | set => SetAttributeLower("name", value); 25 | } 26 | 27 | /// 28 | /// Constructor 29 | /// 30 | public Slot() : base("slot") 31 | { 32 | } 33 | 34 | internal bool MatchesName(string? slotName) 35 | { 36 | var name = Name; 37 | if (string.IsNullOrEmpty(name)) 38 | { 39 | return string.IsNullOrEmpty(slotName); 40 | } 41 | 42 | return name == slotName; 43 | } 44 | 45 | internal override IEnumerable GetLightSlotted() 46 | { 47 | return TryFindParentComponent(this, out var component) ? component.GetSlottedElements(Name) : Enumerable.Repeat(this, 1); 48 | } 49 | 50 | private static bool TryFindParentComponent(Node element, [NotNullWhen(true)] out WebComponent? component) 51 | { 52 | var parent = element.ParentElement; 53 | if (parent is null) 54 | { 55 | component = default; 56 | return false; 57 | } 58 | 59 | if (parent is not Shadow shadow) return TryFindParentComponent(parent, out component); 60 | component = shadow.ParentComponent; 61 | return true; 62 | // ReSharper disable once TailRecursiveCall 63 | } 64 | 65 | internal override bool IsPrintable => false; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LaraUI/Components/SlottedCalculator.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | internal static class SlottedCalculator 10 | { 11 | public static void UpdateSlotted(Node node) 12 | { 13 | node.IsSlotted = IsParentSlotting(node); 14 | if (node is WebComponent component) 15 | { 16 | var shadow = component.GetShadow(); 17 | UpdateSlotted(shadow); 18 | } 19 | if (node is Element element) 20 | { 21 | UpdateChildren(element); 22 | } 23 | } 24 | 25 | internal static bool IsParentSlotting(Node node) 26 | { 27 | var parent = node.ParentElement; 28 | if (parent == null || parent is Slot) 29 | { 30 | return false; 31 | } 32 | if (parent is Shadow shadow) 33 | { 34 | return shadow.ParentComponent.IsSlotted; 35 | } 36 | if (parent is WebComponent component) 37 | { 38 | return node is Element element 39 | && component.IsSlotActive(element.GetAttributeLower("slot")); 40 | } 41 | return parent.IsSlotted; 42 | } 43 | 44 | private static void UpdateChildren(Element element) 45 | { 46 | foreach (var node in element.Children) 47 | { 48 | UpdateSlotted(node); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LaraUI/Components/WebComponentOptions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Options for publishing web components 13 | /// 14 | public sealed class WebComponentOptions 15 | { 16 | /// 17 | /// Custom tag name for the component. Needs to include the '-' character. 18 | /// 19 | public string ComponentTagName { get; set; } = string.Empty; 20 | 21 | /// 22 | /// Type of the component. Needs to inherit from WebComponent. Example: 'typeof(MyComponent)' 23 | /// 24 | public Type? ComponentType { get; set; } 25 | 26 | internal Type GetComponentType() 27 | { 28 | return ComponentType ?? throw new MissingMemberException(nameof(WebComponentOptions), nameof(ComponentType)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/BlockOptions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// Defines options for blocking the UI while executing an event 11 | /// 12 | public class BlockOptions 13 | { 14 | /// 15 | /// Gets or sets the ID of element to block. Leave empty to block the whole page. 16 | /// 17 | /// 18 | /// The ID of the element to block. If left blank, block the entire page. 19 | /// 20 | public string? BlockedElementId { get; set; } 21 | 22 | /// 23 | /// Gets or sets the ID of the element to show. If set, the element specified will be shown instead of the default block dialog. 24 | /// 25 | /// 26 | /// The ID of the element to show instead of the default block dialog. 27 | /// 28 | public string? ShowElementId { get; set; } 29 | 30 | /// 31 | /// Gets or sets an HTML message to show while blocking the user interface. If set, the specified HTML will be shown instead of the default block dialog. 32 | /// 33 | /// 34 | /// The HTML message to show instead of the default block dialog. 35 | /// 36 | public string? ShowHtmlMessage { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/ChildrenBindingSubscription.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Specialized; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal class ChildrenBindingSubscription 12 | { 13 | public NotifyCollectionChangedEventHandler Handler { get; } 14 | public INotifyCollectionChanged Source { get; } 15 | 16 | public ChildrenBindingSubscription( 17 | NotifyCollectionChangedEventHandler handler, 18 | INotifyCollectionChanged source) 19 | { 20 | Handler = handler; 21 | Source = source; 22 | source.CollectionChanged += handler; 23 | } 24 | 25 | public void Unsubscribe() 26 | { 27 | Source.CollectionChanged -= Handler; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/DocumentIdMap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal sealed class DocumentIdMap 12 | { 13 | private readonly Dictionary _map; 14 | 15 | public DocumentIdMap() 16 | { 17 | _map = new Dictionary(); 18 | } 19 | 20 | public bool TryGetElementById(string id, out Element element) 21 | { 22 | return _map.TryGetValue(id, out element); 23 | } 24 | 25 | public void NotifyChangeId(Element element, string before, string after) 26 | { 27 | if (before == after) return; 28 | RemovePrevious(before); 29 | AddAfter(element, after); 30 | } 31 | 32 | private void RemovePrevious(string before) 33 | { 34 | _map.Remove(before); 35 | } 36 | 37 | private void AddAfter(Element element, string after) 38 | { 39 | if (_map.ContainsKey(after)) 40 | { 41 | throw DuplicateElementIdException.Create(after); 42 | } 43 | _map.Add(after, element); 44 | } 45 | 46 | public void NotifyRemoved(Element element) 47 | { 48 | RemovePrevious(element.Id); 49 | } 50 | 51 | public void NotifyAdded(Element element) 52 | { 53 | AddAfter(element, element.Id); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/DuplicateElementIdException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Exception thrown when adding a duplicate element ID into a document 13 | /// 14 | /// 15 | public class DuplicateElementIdException : InvalidOperationException 16 | { 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | // ReSharper disable once UnusedMember.Global 21 | public DuplicateElementIdException() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// The message that describes the error. 29 | // ReSharper disable once MemberCanBePrivate.Global 30 | public DuplicateElementIdException(string message) 31 | : base(message) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// 38 | /// The message. 39 | /// The inner. 40 | public DuplicateElementIdException(string message, Exception inner) 41 | : base(message, inner) 42 | { 43 | } 44 | 45 | /// 46 | /// Creates a DuplicateElementId exception 47 | /// 48 | /// The identifier that is duplicate. 49 | /// Exception created 50 | public static DuplicateElementIdException Create(string id) 51 | { 52 | var message = $"Duplicate element Id: {id}"; 53 | return new DuplicateElementIdException(message); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/GlobalSerializer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Globalization; 8 | using System.Threading; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal static class GlobalSerializer 13 | { 14 | private static long _counter; 15 | 16 | public static string GenerateElementId() 17 | { 18 | Interlocked.Increment(ref _counter); 19 | return "_g" + _counter.ToString("X", CultureInfo.InvariantCulture); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaraUI/DOM/HtmlReference.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal static class HtmlReference 12 | { 13 | private static readonly HashSet _SelfClosingTags; 14 | private static readonly HashSet _DoesRequireId; 15 | 16 | static HtmlReference() 17 | { 18 | _SelfClosingTags = new HashSet 19 | { 20 | "area", 21 | "base", 22 | "br", 23 | "col", 24 | "command", 25 | "embed", 26 | "hr", 27 | "img", 28 | "input", 29 | "keygen", 30 | "link", 31 | "meta", 32 | "param", 33 | "source", 34 | "track", 35 | "wbr" 36 | }; 37 | _DoesRequireId = new HashSet 38 | { 39 | "input", "textarea", "select", "button", "option" 40 | }; 41 | } 42 | 43 | public static bool IsSelfClosingTag(string tagNameLower) 44 | => _SelfClosingTags.Contains(tagNameLower); 45 | 46 | public static bool RequiresId(string tagNameLower) 47 | => _DoesRequireId.Contains(tagNameLower); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/AttributeEditedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class AttributeEditedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string Attribute { get; set; } = string.Empty; 19 | 20 | [DataMember] 21 | public string Value { get; set; } = string.Empty; 22 | 23 | public AttributeEditedDelta() : base(DeltaType.EditAttribute) 24 | { 25 | } 26 | 27 | public static void Enqueue(Element element, string attribute, string? value) 28 | { 29 | if (element.TryGetQueue(out var document)) 30 | { 31 | document.Enqueue(new AttributeEditedDelta 32 | { 33 | Attribute = attribute, 34 | ElementId = element.Id, 35 | Value = value ?? "" 36 | }); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/AttributeRemovedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class AttributeRemovedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string Attribute { get; set; } = string.Empty; 19 | 20 | public AttributeRemovedDelta() : base(DeltaType.RemoveAttribute) 21 | { 22 | } 23 | 24 | public static void Enqueue(Element element, string attribute) 25 | { 26 | if (element.TryGetQueue(out var document)) 27 | { 28 | document.Enqueue(new AttributeRemovedDelta 29 | { 30 | ElementId = element.Id, 31 | Attribute = attribute 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/BaseDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal enum DeltaType 12 | { 13 | Append = 1, 14 | Insert = 2, 15 | TextModified = 3, 16 | Remove = 4, 17 | EditAttribute = 5, 18 | RemoveAttribute = 6, 19 | Focus = 7, 20 | SetId = 8, 21 | SetValue = 9, 22 | SubmitJs = 10, 23 | SetChecked = 11, 24 | ClearChildren = 12, 25 | Replace = 13, 26 | ServerEvents = 14, 27 | SwapChildren = 15, 28 | Subscribe = 16, 29 | Unsubscribe = 17, 30 | RemoveElement = 18, 31 | Render = 19, 32 | UnRender = 20 33 | } 34 | 35 | [DataContract] 36 | [KnownType(typeof(NodeAddedDelta))] 37 | [KnownType(typeof(NodeInsertedDelta))] 38 | [KnownType(typeof(TextModifiedDelta))] 39 | [KnownType(typeof(NodeRemovedDelta))] 40 | [KnownType(typeof(AttributeEditedDelta))] 41 | [KnownType(typeof(AttributeRemovedDelta))] 42 | [KnownType(typeof(FocusDelta))] 43 | [KnownType(typeof(SetIdDelta))] 44 | [KnownType(typeof(SetValueDelta))] 45 | [KnownType(typeof(SubmitJsDelta))] 46 | [KnownType(typeof(SetCheckedDelta))] 47 | [KnownType(typeof(ClearChildrenDelta))] 48 | [KnownType(typeof(ReplaceDelta))] 49 | [KnownType(typeof(ServerEventsDelta))] 50 | [KnownType(typeof(SwapChildrenDelta))] 51 | [KnownType(typeof(SubscribeDelta))] 52 | [KnownType(typeof(UnsubscribeDelta))] 53 | [KnownType(typeof(RemoveElementDelta))] 54 | [KnownType(typeof(RenderDelta))] 55 | [KnownType(typeof(UnRenderDelta))] 56 | internal abstract class BaseDelta 57 | { 58 | [DataMember] 59 | public DeltaType Type { get; set; } 60 | 61 | protected BaseDelta(DeltaType type) 62 | { 63 | Type = type; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ClearChildrenDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class ClearChildrenDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | public ClearChildrenDelta() : base(DeltaType.ClearChildren) 18 | { 19 | } 20 | 21 | public static void Enqueue(Element element) 22 | { 23 | if (element.TryGetQueue(out var document)) 24 | { 25 | document.Enqueue(new ClearChildrenDelta 26 | { 27 | ElementId = element.Id, 28 | }); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentArrayNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal sealed class ContentArrayNode : ContentNode 14 | { 15 | [DataMember] 16 | public List? Nodes { get; set; } 17 | 18 | public ContentArrayNode() : base(ContentNodeType.Array) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentAttribute.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class ContentAttribute 13 | { 14 | [DataMember] 15 | public string Attribute { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string Value { get; set; } = string.Empty; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentElementNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal sealed class ContentElementNode : ContentNode 14 | { 15 | [DataMember] 16 | public string TagName { get; set; } = string.Empty; 17 | 18 | [DataMember] 19 | // ReSharper disable once InconsistentNaming 20 | public string? NS { get; set; } 21 | 22 | [DataMember] 23 | public List? Attributes { get; set; } 24 | 25 | [DataMember] 26 | public List? Children { get; set; } 27 | 28 | public ContentElementNode() : base(ContentNodeType.Element) 29 | { 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal enum ContentNodeType 12 | { 13 | Element = 1, 14 | Text = 2, 15 | Array = 3, 16 | Placeholder = 4 17 | } 18 | 19 | [DataContract] 20 | [KnownType(typeof(ContentTextNode))] 21 | [KnownType(typeof(ContentElementNode))] 22 | [KnownType(typeof(ContentArrayNode))] 23 | [KnownType(typeof(ContentPlaceholder))] 24 | internal abstract class ContentNode 25 | { 26 | [DataMember] 27 | public ContentNodeType Type { get; set; } 28 | 29 | protected ContentNode(ContentNodeType type) 30 | { 31 | Type = type; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentPlaceholder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 1/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal class ContentPlaceholder : ContentNode 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | public ContentPlaceholder() : base(ContentNodeType.Placeholder) 18 | { 19 | } 20 | 21 | public ContentPlaceholder(string id) : this() 22 | { 23 | ElementId = id; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ContentTextNode.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class ContentTextNode : ContentNode 13 | { 14 | [DataMember] 15 | public string? Data { get; set; } 16 | 17 | public ContentTextNode() : base(ContentNodeType.Text) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ElementValue.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class ElementEventValue 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string Value { get; set; } = string.Empty; 19 | 20 | [DataMember] 21 | public bool Checked { get; set; } 22 | 23 | public override string ToString() 24 | { 25 | return $"#{ElementId}='{Value}'{GetCheckedSuffix()}"; 26 | } 27 | 28 | private string GetCheckedSuffix() 29 | { 30 | return Checked ? " checked" : string.Empty; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/EventResult.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal enum EventResultType 13 | { 14 | Success = 0, 15 | NoSession = 1, 16 | NoElement = 2, 17 | OutOfSequence = 3 18 | } 19 | 20 | [DataContract] 21 | internal sealed class EventResult 22 | { 23 | [DataMember] 24 | public EventResultType ResultType { get; set; } 25 | 26 | [DataMember] 27 | public List? List { get; set; } 28 | 29 | public EventResult() 30 | { 31 | } 32 | 33 | public EventResult(List list) : this() 34 | { 35 | List = list; 36 | } 37 | 38 | // ReSharper disable once InconsistentNaming 39 | public string ToJSON() 40 | { 41 | return LaraTools.Serialize(this); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/FocusDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class FocusDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | public FocusDelta() : base(DeltaType.Focus) 18 | { 19 | } 20 | 21 | public static void Enqueue(Element element) 22 | { 23 | element.Document?.Enqueue(new FocusDelta 24 | { 25 | ElementId = element.Id 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/NodeAddedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class NodeAddedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ParentId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public ContentNode? Node { get; set; } 19 | 20 | public NodeAddedDelta() : base(DeltaType.Append) 21 | { 22 | } 23 | 24 | public static void Enqueue(Node node) 25 | { 26 | var parent = node.ParentElement; 27 | if (parent == null || !parent.TryGetQueue(out var document)) return; 28 | var parentId = parent.Id; 29 | var content = node.GetContentNode(); 30 | document.Enqueue(new NodeAddedDelta 31 | { 32 | ParentId = parentId, 33 | Node = content, 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/NodeInsertedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class NodeInsertedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ParentElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public int Index { get; set; } 19 | 20 | [DataMember] 21 | public ContentNode? Node { get; set; } 22 | 23 | public NodeInsertedDelta() : base(DeltaType.Insert) 24 | { 25 | } 26 | 27 | public static void Enqueue(Node node, int index) 28 | { 29 | var parent = node.ParentElement; 30 | if (parent != null && parent.TryGetQueue(out var document)) 31 | { 32 | document.Enqueue(new NodeInsertedDelta 33 | { 34 | ParentElementId = parent.Id, 35 | Index = index, 36 | Node = node.GetContentNode() 37 | }); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/NodeLocator.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal sealed class NodeLocator 14 | { 15 | [DataMember] 16 | public string StartingId { get; set; } = string.Empty; 17 | 18 | [DataMember] 19 | public int? ChildIndex { get; set; } 20 | 21 | public static NodeLocator FromNode(Node node) 22 | { 23 | if (node is Element element) 24 | { 25 | return new NodeLocator 26 | { 27 | StartingId = element.Id 28 | }; 29 | } 30 | var parent = node.ParentElement; 31 | if (parent == null) 32 | { 33 | throw new ArgumentException("NodeLocator from orphan non-element node"); 34 | } 35 | return new NodeLocator 36 | { 37 | StartingId = parent.Id, 38 | ChildIndex = parent.GetChildNodePosition(node) 39 | }; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/NodeRemovedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class NodeRemovedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ParentId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public int ChildIndex { get; set; } 19 | 20 | public NodeRemovedDelta() : base(DeltaType.Remove) 21 | { 22 | } 23 | 24 | public static void Enqueue(Element parent, int index) 25 | { 26 | if (parent.TryGetQueue(out var document)) 27 | { 28 | document.Enqueue(new NodeRemovedDelta 29 | { 30 | ParentId = parent.Id, 31 | ChildIndex = index, 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/PlugOptions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class PlugOptions 13 | { 14 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 15 | public bool Block { get; set; } 16 | 17 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 18 | public string? BlockElementId { get; set; } 19 | 20 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 21 | // ReSharper disable once InconsistentNaming 22 | public string? BlockHTML { get; set; } 23 | 24 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 25 | public string? BlockShownId { get; set; } 26 | 27 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 28 | public bool LongRunning { get; set; } 29 | 30 | [DataMember(IsRequired = false, EmitDefaultValue = false)] 31 | public bool UploadFiles { get; set; } 32 | 33 | public PlugOptions() 34 | { 35 | } 36 | 37 | public PlugOptions(EventSettings settings) 38 | { 39 | Block = settings.Block; 40 | if (settings.BlockOptions != null) 41 | { 42 | BlockElementId = settings.BlockOptions.BlockedElementId; 43 | BlockHTML = settings.BlockOptions.ShowHtmlMessage; 44 | BlockShownId = settings.BlockOptions.ShowElementId; 45 | } 46 | LongRunning = settings.LongRunning; 47 | UploadFiles = settings.UploadFiles; 48 | } 49 | 50 | // ReSharper disable once InconsistentNaming 51 | public string ToJSON() => LaraTools.Serialize(this); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/RemoveElementDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 1/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class RemoveElementDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | public RemoveElementDelta() : base(DeltaType.RemoveElement) 18 | { 19 | } 20 | 21 | public static void Enqueue(Element element) 22 | { 23 | if (element.TryGetQueue(out var document)) 24 | { 25 | document.Enqueue(new RemoveElementDelta 26 | { 27 | ElementId = element.Id 28 | }); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/RenderDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 1/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal class RenderDelta : BaseDelta 14 | { 15 | [DataMember] 16 | public NodeLocator? Locator { get; set; } 17 | 18 | [DataMember] 19 | public ContentNode? Node { get; set; } 20 | 21 | public RenderDelta() : base(DeltaType.Render) 22 | { 23 | } 24 | 25 | public static void Enqueue(Document document, IEnumerable nodes) 26 | { 27 | foreach (var node in nodes) 28 | { 29 | document.Enqueue(new RenderDelta 30 | { 31 | Locator = NodeLocator.FromNode(node), 32 | Node = node.GetContentNode() 33 | }); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ReplaceDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class ReplaceDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string? Location { get; set; } 16 | 17 | public ReplaceDelta() : base(DeltaType.Replace) 18 | { 19 | } 20 | 21 | public static void Enqueue(Document document, string location) 22 | { 23 | document.Enqueue(new ReplaceDelta 24 | { 25 | Location = location 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/ServerEventsDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal class ServerEventsDelta : BaseDelta 13 | { 14 | public ServerEventsDelta() : base(DeltaType.ServerEvents) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/SetCheckedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class SetCheckedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public bool Checked { get; set; } 19 | 20 | public SetCheckedDelta() : base(DeltaType.SetChecked) 21 | { 22 | } 23 | 24 | public static void Enqueue(Element element, bool value) 25 | { 26 | if (element.TryGetQueue(out var document)) 27 | { 28 | document.Enqueue(new SetCheckedDelta 29 | { 30 | ElementId = element.Id, 31 | Checked = value 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/SetIdDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class SetIdDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string OldId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string NewId { get; set; } = string.Empty; 19 | 20 | public SetIdDelta() : base(DeltaType.SetId) 21 | { 22 | } 23 | 24 | public static void Enqueue(Element element, string newValue) 25 | { 26 | if (element.TryGetQueue(out var document)) 27 | { 28 | document.Enqueue(new SetIdDelta 29 | { 30 | OldId = element.Id, 31 | NewId = newValue 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/SetValueDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class SetValueDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string? Value { get; set; } 19 | 20 | public SetValueDelta() : base(DeltaType.SetValue) 21 | { 22 | } 23 | 24 | public static void Enqueue(Element element, string? value) 25 | { 26 | if (element.TryGetQueue(out var document)) 27 | { 28 | document.Enqueue(new SetValueDelta 29 | { 30 | ElementId = element.Id, 31 | Value = value 32 | }); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/SubmitJsDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class SubmitJsDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string Code { get; set; } = string.Empty; 16 | 17 | [DataMember(EmitDefaultValue = false)] 18 | public string? Payload { get; set; } 19 | 20 | public SubmitJsDelta() : base(DeltaType.SubmitJs) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/SwapChildrenDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class SwapChildrenDelta : BaseDelta 13 | { 14 | public SwapChildrenDelta() : base(DeltaType.SwapChildren) 15 | { 16 | } 17 | 18 | [DataMember] 19 | public string ParentId { get; set; } = string.Empty; 20 | 21 | [DataMember] 22 | public int Index1 { get; set; } 23 | 24 | [DataMember] 25 | public int Index2 { get; set; } 26 | 27 | public static void Enqueue(Element parent, int index1, int index2) 28 | { 29 | if (parent.TryGetQueue(out var document)) 30 | { 31 | document.Enqueue(new SwapChildrenDelta 32 | { 33 | ParentId = parent.Id, 34 | Index1 = index1, 35 | Index2 = index2 36 | }); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/TextModifiedDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal sealed class TextModifiedDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ParentElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public int ChildNodeIndex { get; set; } 19 | 20 | [DataMember] 21 | public string? Text { get; set; } 22 | 23 | public TextModifiedDelta() : base(DeltaType.TextModified) 24 | { 25 | } 26 | 27 | public static void Enqueue(TextNode node) 28 | { 29 | var parent = node.ParentElement; 30 | if (parent == null || !parent.TryGetQueue(out var document)) return; 31 | var index = parent.GetChildNodePosition(node); 32 | document.Enqueue(new TextModifiedDelta 33 | { 34 | ParentElementId = parent.Id, 35 | ChildNodeIndex = index, 36 | Text = node.Data 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/UnRenderDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 1/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal class UnRenderDelta : BaseDelta 14 | { 15 | [DataMember] 16 | public NodeLocator? Locator { get; set; } 17 | 18 | public UnRenderDelta() : base(DeltaType.UnRender) 19 | { 20 | } 21 | 22 | public static void Enqueue(Document document, IEnumerable nodes) 23 | { 24 | foreach (var node in nodes) 25 | { 26 | document.Enqueue(new UnRenderDelta 27 | { 28 | Locator = NodeLocator.FromNode(node) 29 | }); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LaraUI/Delta/UnsubscribeDelta.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 10/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Runtime.Serialization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | [DataContract] 12 | internal class UnsubscribeDelta : BaseDelta 13 | { 14 | [DataMember] 15 | public string ElementId { get; set; } = string.Empty; 16 | 17 | [DataMember] 18 | public string EventName { get; set; } = string.Empty; 19 | 20 | public UnsubscribeDelta() : base(DeltaType.Unsubscribe) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/GenericElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// A generic element class for all elements that are not handled by specialized classes. 11 | /// 12 | /// 13 | public sealed class GenericElement : Element 14 | { 15 | internal GenericElement(string tagName) : base(tagName) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlBodyElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 12/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Body element 14 | /// 15 | [Obsolete("Use HtmlBodyElement")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class BodyElement : HtmlBodyElement 18 | { 19 | } 20 | 21 | /// 22 | /// HTML 'body' element 23 | /// 24 | public class HtmlBodyElement : Element 25 | { 26 | /// 27 | /// Constructor 28 | /// 29 | public HtmlBodyElement() : base("body") 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlColGroupElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// ColGroup element 14 | /// 15 | [Obsolete("Use HtmlColGroupElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class ColGroup : HtmlColGroupElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'colgroup' HTML5 element 23 | /// 24 | /// 25 | public class HtmlColGroupElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlColGroupElement() : base("colgroup") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'span' HTML5 attribute. 36 | /// 37 | public int? Span 38 | { 39 | get => GetIntAttribute("span"); 40 | set => SetIntAttribute("span", value); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlDivElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// HTML div element 11 | /// 12 | public class HtmlDivElement : Element 13 | { 14 | /// 15 | /// Constructor 16 | /// 17 | public HtmlDivElement() : base("div") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlHeadElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 12/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Head element 14 | /// 15 | [Obsolete("Use HtmlHeadElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class HeadElement : HtmlHeadElement 18 | { 19 | } 20 | 21 | /// 22 | /// HTML 'head' element 23 | /// 24 | /// 25 | public class HtmlHeadElement : Element 26 | { 27 | /// 28 | /// Constructor 29 | /// 30 | public HtmlHeadElement() : base("head") 31 | { 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlHeadingElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Represents the h1..h6 tags 13 | /// 14 | public class HtmlHeadingElement : Element 15 | { 16 | /// 17 | /// Constructor 18 | /// 19 | /// 20 | public HtmlHeadingElement(int level) : base(GetLevelTag(level)) 21 | { 22 | } 23 | 24 | /// 25 | /// Returns h1..h6 for levels 1..6 26 | /// 27 | /// 28 | /// 29 | public static string GetLevelTag(int level) 30 | { 31 | if (level < 1 || level > 6) 32 | { 33 | throw new ArgumentException("Invalid heading level"); 34 | } 35 | return $"h{level}"; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlLabelElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Label element 14 | /// 15 | [Obsolete("Use HtmlLabelElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class Label : HtmlLabelElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'label' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlLabelElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlLabelElement() : base("label") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'for' HTML5 attribute. 36 | /// 37 | public string? For 38 | { 39 | get => GetAttributeLower("for"); 40 | set => SetAttributeLower("for", value); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlLiElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// LI element 14 | /// 15 | [Obsolete("Use HtmlLiElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class ListItem : HtmlLiElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'li' HTML5 element 23 | /// 24 | /// 25 | public class HtmlLiElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlLiElement() : base("li") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'value' property. 36 | /// 37 | public string? Value 38 | { 39 | get => GetAttributeLower("value"); 40 | set => SetAttributeLower("value", value); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlMetaElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Meta element 14 | /// 15 | [Obsolete("Use HtmlMetaElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class Meta : HtmlMetaElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'meta' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlMetaElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlMetaElement() : base("meta") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'content' HTML5 attribute. 36 | /// 37 | public string? Content 38 | { 39 | get => GetAttributeLower("content"); 40 | set => SetAttributeLower("content", value); 41 | } 42 | 43 | /// 44 | /// Gets or sets the 'httpequiv' HTML5 attribute. 45 | /// 46 | public string? HttpEquiv 47 | { 48 | get => GetAttributeLower("http-equiv"); 49 | set => SetAttributeLower("http-equiv", value); 50 | } 51 | 52 | /// 53 | /// Gets or sets the 'name' HTML5 attribute. 54 | /// 55 | public string? Name 56 | { 57 | get => GetAttributeLower("name"); 58 | set => SetAttributeLower("name", value); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlMeterElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Meter element 14 | /// 15 | [Obsolete("Use HtmlMeterElement")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class Meter : HtmlMeterElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'meter' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlMeterElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlMeterElement() : base("meter") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'high' HTML5 attribute. 36 | /// 37 | public int? High 38 | { 39 | get => GetIntAttribute("high"); 40 | set => SetIntAttribute("high", value); 41 | } 42 | 43 | /// 44 | /// Gets or sets the 'low' HTML5 attribute. 45 | /// 46 | public int? Low 47 | { 48 | get => GetIntAttribute("low"); 49 | set => SetIntAttribute("low", value); 50 | } 51 | 52 | /// 53 | /// Gets or sets the 'max' HTML5 attribute. 54 | /// 55 | public int? Max 56 | { 57 | get => GetIntAttribute("max"); 58 | set => SetIntAttribute("max", value); 59 | } 60 | 61 | /// 62 | /// Gets or sets the 'min' HTML5 attribute. 63 | /// 64 | public int? Min 65 | { 66 | get => GetIntAttribute("min"); 67 | set => SetIntAttribute("min", value); 68 | } 69 | 70 | /// 71 | /// Gets or sets the 'optimum' HTML5 attribute. 72 | /// 73 | public int? Optimum 74 | { 75 | get => GetIntAttribute("optimum"); 76 | set => SetIntAttribute("optimum", value); 77 | } 78 | 79 | /// 80 | /// Gets or sets the 'value' HTML5 attribute. 81 | /// 82 | public int? Value 83 | { 84 | get => GetIntAttribute("value"); 85 | set => SetIntAttribute("value", value); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlOlElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Ordered list 14 | /// 15 | [Obsolete("Use HtmlOlElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class OrderedList : HtmlOlElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'ol' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlOlElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlOlElement() : base("ol") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'reversed' HTML5 attribute. 36 | /// 37 | public bool Reversed 38 | { 39 | get => HasAttributeLower("reversed"); 40 | set => SetFlagAttributeLower("reversed", value); 41 | } 42 | 43 | /// 44 | /// Gets or sets the 'start' HTML5 attribute. 45 | /// 46 | public int? Start 47 | { 48 | get => GetIntAttribute("start"); 49 | set => SetIntAttribute("start", value); 50 | } 51 | 52 | /// 53 | /// Gets or sets the 'type' HTML5 attribute. 54 | /// 55 | public string? Type 56 | { 57 | get => GetAttributeLower("type"); 58 | set => SetAttributeLower("type", value); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlOptionElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Option element 14 | /// 15 | [Obsolete("Use HtmlOptionElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class OptionElement : HtmlOptionElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'option' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlOptionElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlOptionElement() : base("option") 31 | { 32 | } 33 | 34 | internal override void NotifyValue(ElementEventValue entry) 35 | { 36 | BeginUpdate(); 37 | base.NotifyValue(entry); 38 | NotifySelected(entry.Checked); 39 | EndUpdate(); 40 | } 41 | 42 | /// 43 | /// Gets or sets the 'disabled' HTML5 attribute. 44 | /// 45 | public bool Disabled 46 | { 47 | get => HasAttributeLower("disabled"); 48 | set => SetFlagAttributeLower("disabled", value); 49 | } 50 | 51 | /// 52 | /// Gets or sets the 'label' HTML5 attribute. 53 | /// 54 | public string? Label 55 | { 56 | get => GetAttributeLower("label"); 57 | set => SetAttributeLower("label", value); 58 | } 59 | 60 | /// 61 | /// Gets or sets the 'selected' HTML5 attribute. 62 | /// 63 | public bool Selected 64 | { 65 | get => HasAttributeLower("selected"); 66 | set => SetFlagAttributeLower("selected", value); 67 | } 68 | 69 | /// 70 | /// Gets or sets the 'value' HTML5 attribute. 71 | /// 72 | public string? Value 73 | { 74 | get => GetAttributeLower("value"); 75 | set => SetAttributeLower("value", value); 76 | } 77 | 78 | internal void NotifyAdded(string parentValue) 79 | { 80 | if (parentValue == Value) 81 | { 82 | Selected = true; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlParagraphElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// HTML p element 11 | /// 12 | public class HtmlParagraphElement : Element 13 | { 14 | /// 15 | /// constructor 16 | /// 17 | public HtmlParagraphElement() : base("p") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlScriptElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Script element 14 | /// 15 | [Obsolete("Use HtmlScriptElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class Script : HtmlScriptElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'script' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlScriptElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlScriptElement() : base("script") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'async' HTML5 attribute. 36 | /// 37 | public bool Async 38 | { 39 | get => HasAttributeLower("async"); 40 | set => SetFlagAttributeLower("async", value); 41 | } 42 | 43 | /// 44 | /// Gets or sets the 'charset' HTML5 attribute. 45 | /// 46 | public string? Charset 47 | { 48 | get => GetAttributeLower("charset"); 49 | set => SetAttributeLower("charset", value); 50 | } 51 | 52 | /// 53 | /// Gets or sets the 'defer' HTML5 attribute. 54 | /// 55 | public bool Defer 56 | { 57 | get => HasAttributeLower("defer"); 58 | set => SetFlagAttributeLower("defer", value); 59 | } 60 | 61 | /// 62 | /// Gets or sets the 'src' HTML5 attribute. 63 | /// 64 | public string? Src 65 | { 66 | get => GetAttributeLower("src"); 67 | set => SetAttributeLower("src", value); 68 | } 69 | 70 | /// 71 | /// Gets or sets the 'type' HTML5 attribute. 72 | /// 73 | public string? Type 74 | { 75 | get => GetAttributeLower("type"); 76 | set => SetAttributeLower("type", value); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlSpanElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// HTML span element 11 | /// 12 | public class HtmlSpanElement : Element 13 | { 14 | /// 15 | /// Constructor 16 | /// 17 | public HtmlSpanElement() : base("span") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlTableCellElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Table cell element 14 | /// 15 | [Obsolete("Use HtmlTableCellElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class TableCell : HtmlTableCellElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'td' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlTableCellElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlTableCellElement() : base("td") 31 | { 32 | } 33 | 34 | /// 35 | /// Gets or sets the 'colspan' HTML5 attribute. 36 | /// 37 | public int? ColSpan 38 | { 39 | get => GetIntAttribute("colspan"); 40 | set => SetIntAttribute("colspan", value); 41 | } 42 | 43 | /// 44 | /// Gets or sets the 'headers' HTML5 attribute. 45 | /// 46 | public string? Headers 47 | { 48 | get => GetAttributeLower("headers"); 49 | set => SetAttributeLower("headers", value); 50 | } 51 | 52 | /// 53 | /// Gets or sets the 'rowspan' HTML5 attribute. 54 | /// 55 | public int? RowSpan 56 | { 57 | get => GetIntAttribute("rowspan"); 58 | set => SetIntAttribute("rowspan", value); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlTableElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Table element 14 | /// 15 | [Obsolete("Use HtmlTableElement instead")] 16 | [EditorBrowsable(EditorBrowsableState.Never)] 17 | public class Table : HtmlTableElement 18 | { 19 | } 20 | 21 | /// 22 | /// The 'table' HTML5 element. 23 | /// 24 | /// 25 | public class HtmlTableElement : Element 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public HtmlTableElement() : base("table") 31 | { 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlTableRowElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 2/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// The 'tr' HTML5 element. 11 | /// 12 | public class HtmlTableRowElement : Element 13 | { 14 | /// 15 | /// Constructor 16 | /// 17 | public HtmlTableRowElement() : base("tr") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlTableSectionElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// HTML table section types 13 | /// 14 | public enum HtmlTableSectionType 15 | { 16 | /// 17 | /// Table body 18 | /// 19 | Body, 20 | 21 | /// 22 | /// Table header 23 | /// 24 | Head, 25 | 26 | /// 27 | /// Table footer 28 | /// 29 | Foot 30 | } 31 | 32 | /// 33 | /// HTML table body element 34 | /// 35 | public class HtmlTableSectionElement : Element 36 | { 37 | /// 38 | /// Associates a table section type with an element tag 39 | /// 40 | private static readonly Dictionary _SectionTags 41 | = new() 42 | { 43 | { HtmlTableSectionType.Body, "tbody" }, 44 | { HtmlTableSectionType.Head, "thead" }, 45 | { HtmlTableSectionType.Foot, "tfoot" } 46 | }; 47 | 48 | /// 49 | /// constructor 50 | /// 51 | public HtmlTableSectionElement(HtmlTableSectionType type) 52 | : base(_SectionTags[type]) 53 | { 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LaraUI/Elements/HtmlTitleElement.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Integrative Software LLC 3 | Created: 2/2021 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | /// 10 | /// HTML5 'title' element 11 | /// 12 | public class HtmlTitleElement : Element 13 | { 14 | /// 15 | /// Constructor 16 | /// 17 | public HtmlTitleElement() : base("title") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", 7 | Justification = "No sync context in ASP.NET Core", 8 | Scope = "namespaceanddescendants", 9 | Target = "Integrative.Lara")] 10 | -------------------------------------------------------------------------------- /src/LaraUI/LaraUI.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True -------------------------------------------------------------------------------- /src/LaraUI/Main/BaseContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 10/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal abstract class BaseContext : ILaraContext 12 | { 13 | public HttpContext Http { get; } 14 | public Application Application { get; } 15 | 16 | internal BaseContext(Application app, HttpContext http) 17 | { 18 | LaraUI.InternalContext.Value = this; 19 | Application = app; 20 | Http = http; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/LaraUI/Main/BinaryServiceContent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Binary web service content definitions 13 | /// 14 | public sealed class BinaryServiceContent 15 | { 16 | /// 17 | /// The URL of the web service (e.g. '/MyService') 18 | /// 19 | public string Address { get; set; } = string.Empty; 20 | 21 | /// 22 | /// The HTTP method (e.g. 'POST') 23 | /// 24 | public string Method { get; set; } = "POST"; 25 | 26 | /// 27 | /// The web service's reponse content-type (e.g. 'image/gif') 28 | /// 29 | public string ContentType { get; set; } = string.Empty; 30 | 31 | /// 32 | /// The method for creating instances of the web service class 33 | /// 34 | public Func? Factory { get; set; } 35 | 36 | internal Func GetFactory() 37 | { 38 | return Factory ?? throw new MissingMemberException(nameof(BinaryServiceContent), nameof(Factory)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/LaraUI/Main/BinaryServicePublished.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class BinaryServicePublished : IPublishedItem 14 | { 15 | public Func Factory { get; } 16 | private string ContentType { get; } 17 | 18 | public BinaryServicePublished(BinaryServiceContent content) 19 | { 20 | Factory = content.GetFactory(); 21 | ContentType = content.ContentType; 22 | } 23 | 24 | public async Task Run(Application app, HttpContext http, LaraOptions options) 25 | { 26 | var context = new WebServiceContext(app, http) 27 | { 28 | RequestBody = await MiddlewareCommon.ReadBody(http).ConfigureAwait(false) 29 | }; 30 | var handler = Factory(); 31 | var data = Array.Empty(); 32 | if (await MiddlewareCommon.RunHandler(http, async () => 33 | { 34 | data = await handler.Execute(); 35 | }).ConfigureAwait(false)) 36 | { 37 | await SendReply(context, data); 38 | } 39 | } 40 | 41 | private async Task SendReply(WebServiceContext context, byte[] data) 42 | { 43 | WebServicePublished.SendHeader(context, ContentType); 44 | await MiddlewareCommon.WriteBuffer(context.Http, data); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LaraUI/Main/GlobalConstants.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | namespace Integrative.Lara 8 | { 9 | internal static class GlobalConstants 10 | { 11 | public const string CookieSessionId = "_Lara_SessionId"; 12 | public const string GuidFormat = "N"; 13 | public const string WindowUnload = "_window_unload"; 14 | public const string ServerSideEvent = "_server_event"; 15 | public const string MessageKey = "_message"; 16 | public const string FilePrefix = "file/"; 17 | 18 | #if DEBUG 19 | public const string LibraryAddress = "/LaraUI-v{0}-debug.js"; 20 | #else 21 | public const string LibraryAddress = "/LaraUI-v{0}.js"; 22 | #endif 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LaraUI/Main/IBinaryService.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Binary Service handler class 13 | /// 14 | public interface IBinaryService 15 | { 16 | /// 17 | /// Executes the web service 18 | /// 19 | /// Response's body 20 | Task Execute(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaraUI/Main/INavigation.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Methods related to document navigation 13 | /// 14 | public interface INavigation 15 | { 16 | /// 17 | /// Replaces the specified location. 18 | /// 19 | /// The new URL location. 20 | void Replace(string location); 21 | 22 | /// 23 | /// Flushes the partial changes on the document to the client. Useful to report progress. Use with 'await'. 24 | /// 25 | /// Task 26 | Task FlushPartialChanges(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/LaraUI/Main/IPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Interface for web pages 13 | /// 14 | public interface IPage 15 | { 16 | /// 17 | /// Called when replying to the initial HTTP GET request. 18 | /// 19 | /// Task 20 | Task OnGet(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaraUI/Main/IPublishedItem.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal interface IPublishedItem 13 | { 14 | Task Run(Application app, HttpContext http, LaraOptions options); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/LaraUI/Main/IWebService.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Web Service handler class 13 | /// 14 | public interface IWebService 15 | { 16 | /// 17 | /// Executes the web service 18 | /// 19 | /// Response's body 20 | Task Execute(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LaraUI/Main/IWebServiceContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Net; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Context for web service requests 14 | /// 15 | public interface IWebServiceContext : ILaraContext 16 | { 17 | /// 18 | /// Request's body sent by the client 19 | /// 20 | string RequestBody { get; } 21 | 22 | /// 23 | /// Status code to return 24 | /// 25 | HttpStatusCode StatusCode { get; set; } 26 | 27 | /// 28 | /// Gets a Session object when available 29 | /// 30 | /// Session object 31 | /// true when found 32 | bool TryGetSession([NotNullWhen(true)] out Session? session); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LaraUI/Main/JSBridge.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.ComponentModel; 9 | using System.Threading.Tasks; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class JsBridge : IJsBridge 14 | { 15 | private readonly PageContext _parent; 16 | 17 | public string? EventData { get; internal set; } = string.Empty; 18 | 19 | public JsBridge(PageContext parent) 20 | { 21 | _parent = parent; 22 | } 23 | 24 | public void Submit(string javaScriptCode, string? payload = null) 25 | { 26 | _parent.Document.Enqueue(new SubmitJsDelta 27 | { 28 | Code = javaScriptCode, 29 | Payload = payload 30 | }); 31 | } 32 | 33 | [Obsolete("Use instead AddMessageListener() and RemoveMessageListener().")] 34 | [EditorBrowsable(EditorBrowsableState.Never)] 35 | public void OnMessage(string key, Func handler) 36 | { 37 | _parent.Document.OnMessage(key, handler); 38 | } 39 | 40 | public void AddMessageListener(string messageId, Func handler) 41 | { 42 | _parent.Document.AddMessageListener(messageId, handler); 43 | } 44 | 45 | public void RemoveMessageListener(string messageId, Func handler) 46 | { 47 | _parent.Document.RemoveMessageListener(messageId, handler); 48 | } 49 | 50 | public void ServerEventsOn() => _parent.Document.ServerEventsOn(); 51 | 52 | public Task ServerEventsOff() => _parent.Document.ServerEventsOff(); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/LaraUI/Main/LaraBinaryServiceAttribute.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Marks a class as a web service that replies an array of bytes 13 | /// 14 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 15 | public class LaraBinaryServiceAttribute : Attribute 16 | { 17 | /// 18 | /// Web Service's address (e.g. '/myWS') 19 | /// 20 | public string Address { get; set; } = string.Empty; 21 | 22 | /// 23 | /// Web Service's method (e.g. 'POST') 24 | /// 25 | public string Method { get; set; } = "POST"; 26 | 27 | /// 28 | /// Web Service's response content-type HTTP header (e.g. 'image/gif') 29 | /// 30 | public string ContentType { get; set; } = string.Empty; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Main/LaraPageAttribute.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 7/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Declares a class as a web page that gets published with LaraUI.PublishAssemblies() 13 | /// 14 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 15 | public sealed class LaraPageAttribute : Attribute 16 | { 17 | /// 18 | /// Page's address (e.g. '/myPage') 19 | /// 20 | public string Address { get; set; } = string.Empty; 21 | 22 | /// 23 | /// Default constructor 24 | /// 25 | public LaraPageAttribute() 26 | { 27 | } 28 | 29 | /// 30 | /// Constuctor with address 31 | /// 32 | /// Page's address 33 | public LaraPageAttribute(string address) 34 | : this() 35 | { 36 | Address = address; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LaraUI/Main/LaraWebServiceAttribute.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 7/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Declares a class as a web service that gets published with LaraUI.PublishAssemblies() 13 | /// 14 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 15 | public sealed class LaraWebServiceAttribute : Attribute 16 | { 17 | /// 18 | /// Web Service's address (e.g. '/myWS') 19 | /// 20 | public string Address { get; set; } = string.Empty; 21 | 22 | /// 23 | /// Web Service's method (e.g. 'POST') 24 | /// 25 | public string Method { get; set; } = "POST"; 26 | 27 | /// 28 | /// Web Service's response content-type HTTP header (e.g. 'application/json') 29 | /// 30 | public string ContentType { get; set; } = "application/json"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Main/Navigation.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal sealed class Navigation : INavigation 13 | { 14 | private readonly PageContext _context; 15 | 16 | public string? RedirectLocation { get; private set; } 17 | 18 | public Navigation(PageContext context) 19 | { 20 | _context = context; 21 | } 22 | 23 | public void Replace(string location) 24 | { 25 | if (_context.Http.Request.Method == "GET") 26 | { 27 | ReplaceGet(location); 28 | } 29 | else 30 | { 31 | ReplacePost(location); 32 | } 33 | } 34 | 35 | private void ReplaceGet(string location) 36 | { 37 | RedirectLocation = location; 38 | } 39 | 40 | private void ReplacePost(string location) 41 | { 42 | ReplaceDelta.Enqueue(_context.Document, location); 43 | } 44 | 45 | public async Task FlushPartialChanges() 46 | { 47 | if (_context.Socket == null) 48 | { 49 | throw new InvalidOperationException(Resources.FlushNotAvailable); 50 | } 51 | if (_context.Document.HasPendingChanges) 52 | { 53 | await PostEventHandler.FlushPartialChanges(_context.Socket, _context.Document); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LaraUI/Main/PageContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Net.WebSockets; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class PageContext : BaseContext, IPageContext 14 | { 15 | public Document? DocumentInternal { get; internal set; } 16 | 17 | public Document Document 18 | => DocumentInternal ?? throw new MissingMemberException(nameof(PageContext), nameof(Document)); 19 | 20 | private readonly JsBridge _bridge; 21 | private readonly Navigation _navigation; 22 | private readonly Connection _connection; 23 | 24 | public PageContext(Application app, HttpContext http, Connection connection) 25 | : base(app, http) 26 | { 27 | _navigation = new Navigation(this); 28 | _bridge = new JsBridge(this); 29 | _connection = connection; 30 | } 31 | 32 | public Session Session => _connection.Session; 33 | 34 | internal WebSocket? Socket { get; set; } 35 | 36 | public IJsBridge JSBridge => _bridge; 37 | public INavigation Navigation => _navigation; 38 | 39 | public string? RedirectLocation => _navigation.RedirectLocation; 40 | 41 | internal void SetExtraData(string? data) => _bridge.EventData = data; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/LaraUI/Main/Session.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Session information 13 | /// 14 | public sealed class Session 15 | { 16 | private readonly Connection _parent; 17 | 18 | /// 19 | /// Occurs when the user closes all browser tabs 20 | /// 21 | public event EventHandler? Closing; 22 | 23 | internal event EventHandler? CloseComplete; 24 | 25 | internal Session(Connection parent) 26 | { 27 | _parent = parent; 28 | } 29 | 30 | internal void Close() 31 | { 32 | var args = new EventArgs(); 33 | try 34 | { 35 | Closing?.Invoke(this, args); 36 | } 37 | // ReSharper disable once EmptyGeneralCatchClause 38 | catch 39 | { 40 | } 41 | CloseComplete?.Invoke(this, args); 42 | } 43 | 44 | /// 45 | /// Returns an ID that uniquely identifies the UI session 46 | /// 47 | public Guid SessionId => _parent.Id; 48 | 49 | /// 50 | /// Stores a key value pair 51 | /// 52 | /// Identifier of the value to store 53 | /// Value to store 54 | public void SaveValue(string key, string value) 55 | => _parent.Storage.Save(key, value); 56 | 57 | /// 58 | /// Removes a stored value 59 | /// 60 | /// Identifier of the value stored 61 | public void RemoveValue(string key) 62 | => _parent.Storage.Remove(key); 63 | 64 | /// 65 | /// Retrieves a value stored 66 | /// 67 | /// Identifier of the value stored 68 | /// Value stored 69 | /// true if found, false otherwise 70 | public bool TryGetValue(string key, out string value) 71 | => _parent.Storage.TryGetValue(key, out value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/LaraUI/Main/SessionStorage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal sealed class SessionStorage 12 | { 13 | private readonly Dictionary _values; 14 | private readonly object _mylock; 15 | 16 | public SessionStorage() 17 | { 18 | _values = new Dictionary(); 19 | _mylock = new object(); 20 | } 21 | 22 | public void Save(string key, string value) 23 | { 24 | lock (_mylock) 25 | { 26 | _values.Remove(key); 27 | _values.Add(key, value); 28 | } 29 | } 30 | 31 | public void Remove(string key) 32 | { 33 | lock (_mylock) 34 | { 35 | _values.Remove(key); 36 | } 37 | } 38 | 39 | public bool TryGetValue(string key, out string value) 40 | { 41 | lock (_mylock) 42 | { 43 | return _values.TryGetValue(key, out value); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LaraUI/Main/SingleElementPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal class SingleElementPage : IPage 13 | { 14 | private Func ContentFactory { get; } 15 | 16 | public SingleElementPage(Func contentFactory) 17 | { 18 | ContentFactory = contentFactory; 19 | } 20 | 21 | public Task OnGet() 22 | { 23 | var node = ContentFactory(); 24 | LaraUI.Document.Body.AppendChild(node); 25 | return Task.CompletedTask; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/LaraUI/Main/TemplateBuilder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Globalization; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal static class TemplateBuilder 12 | { 13 | private static readonly string _LibraryUrl; 14 | 15 | static TemplateBuilder() 16 | { 17 | _LibraryUrl = ClientLibraryHandler.GetLibraryPath(); 18 | } 19 | 20 | public static void Build(Document document, double keepAliveInterval) 21 | { 22 | var head = document.Head; 23 | 24 | // lang 25 | document.Lang = "en"; 26 | 27 | // UTF-8 28 | var meta = Element.Create("meta"); 29 | meta.SetAttribute("charset", "utf-8"); 30 | head.AppendChild(meta); 31 | 32 | // LaraClient.js 33 | var script = new HtmlScriptElement 34 | { 35 | Src = _LibraryUrl, 36 | Defer = true 37 | }; 38 | head.AppendChild(script); 39 | 40 | // initialization script 41 | var tag = Element.Create("script"); 42 | var id = document.VirtualId.ToString(GlobalConstants.GuidFormat, CultureInfo.InvariantCulture); 43 | var interval = keepAliveInterval.ToString(CultureInfo.InvariantCulture); 44 | var code = $"document.addEventListener('DOMContentLoaded', function() {{ LaraUI.initialize('{id}', {interval}); }});"; 45 | tag.AppendChild(new TextNode { Data = code }); 46 | head.AppendChild(tag); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LaraUI/Main/WebServiceContent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// Web server content definitions 13 | /// 14 | public sealed class WebServiceContent 15 | { 16 | /// 17 | /// The URL of the web service (e.g. '/MyService') 18 | /// 19 | public string Address { get; set; } = string.Empty; 20 | 21 | /// 22 | /// The HTTP method (e.g. 'POST') 23 | /// 24 | public string Method { get; set; } = "POST"; 25 | 26 | /// 27 | /// The web service's reponse content-type (e.g. 'application/json') 28 | /// 29 | public string ContentType { get; set; } = "application/json"; 30 | 31 | /// 32 | /// The method for creating instances of the web service class 33 | /// 34 | public Func? Factory { get; set; } 35 | 36 | internal Func GetFactory() 37 | { 38 | return Factory ?? throw new MissingMemberException(nameof(WebServiceContent), nameof(Factory)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/LaraUI/Main/WebServiceContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Net; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class WebServiceContext : BaseContext, IWebServiceContext 14 | { 15 | public string RequestBody { get; set; } = string.Empty; 16 | public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK; 17 | 18 | public WebServiceContext(Application app, HttpContext http) 19 | : base(app, http) 20 | { 21 | } 22 | 23 | public bool TryGetSession([NotNullWhen(true)] out Session? session) 24 | { 25 | if (MiddlewareCommon.TryFindConnection(Application, Http, out var connection)) 26 | { 27 | session = connection.Session; 28 | return true; 29 | } 30 | 31 | session = default; 32 | return false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/LaraUI/Main/WebServicePublished.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class WebServicePublished : IPublishedItem 14 | { 15 | public Func Factory { get; } 16 | private string ContentType { get; } 17 | 18 | public WebServicePublished(WebServiceContent content) 19 | { 20 | Factory = content.GetFactory(); 21 | ContentType = content.ContentType; 22 | } 23 | 24 | public async Task Run(Application app, HttpContext http, LaraOptions options) 25 | { 26 | var context = new WebServiceContext(app, http) 27 | { 28 | RequestBody = await MiddlewareCommon.ReadBody(http).ConfigureAwait(false) 29 | }; 30 | var handler = Factory(); 31 | var data = string.Empty; 32 | if (await MiddlewareCommon.RunHandler(http, async () => 33 | { 34 | data = await handler.Execute(); 35 | }).ConfigureAwait(false)) 36 | { 37 | await SendReply(context, data); 38 | } 39 | } 40 | 41 | private async Task SendReply(WebServiceContext context, string data) 42 | { 43 | SendHeader(context, ContentType); 44 | await MiddlewareCommon.WriteUtf8Buffer(context.Http, data); 45 | } 46 | 47 | internal static void SendHeader(WebServiceContext context, string contentType) 48 | { 49 | var http = context.Http; 50 | var headers = http.Response.Headers; 51 | if (!string.IsNullOrEmpty(contentType)) 52 | { 53 | headers.Add("Content-Type", contentType); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/BaseHandler.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal abstract class BaseHandler 13 | { 14 | private readonly RequestDelegate _next; 15 | 16 | protected BaseHandler(RequestDelegate next) 17 | { 18 | _next = next; 19 | } 20 | 21 | public async Task Invoke(HttpContext http) 22 | { 23 | try 24 | { 25 | await TryInvoke(http); 26 | } 27 | catch (StatusCodeException e) 28 | { 29 | var text = $"HTTP error {(int)e.StatusCode} '{e.StatusCode}': {e.Message}"; 30 | await MiddlewareCommon.SendStatusReply(http, e.StatusCode, text); 31 | } 32 | } 33 | 34 | private async Task TryInvoke(HttpContext http) 35 | { 36 | if (!await ProcessRequest(http).ConfigureAwait(false)) 37 | { 38 | await _next.Invoke(http); 39 | } 40 | } 41 | 42 | internal abstract Task ProcessRequest(HttpContext http); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/ClientEventMessage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Integrative.Lara 11 | { 12 | [DataContract] 13 | internal sealed class ClientEventMessage 14 | { 15 | [DataMember] 16 | public List? Values { get; set; } 17 | 18 | [DataMember] 19 | public string? ExtraData { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/DefaultErrorPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 9/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal class DefaultErrorPage : IPage 12 | { 13 | public string Title { get; set; } = string.Empty; 14 | public string Message { get; set; } = string.Empty; 15 | 16 | public Task OnGet() 17 | { 18 | LoadBootstrap(); 19 | ShowContent(); 20 | return Task.CompletedTask; 21 | } 22 | 23 | private static void LoadBootstrap() 24 | { 25 | var head = LaraUI.Page.Document.Head; 26 | head.AppendChild(new HtmlLinkElement 27 | { 28 | Rel = "stylesheet", 29 | HRef = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" 30 | }); 31 | head.AppendChild(new HtmlScriptElement 32 | { 33 | Src = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", 34 | Defer = true 35 | }); 36 | head.AppendChild(new HtmlScriptElement 37 | { 38 | Src = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", 39 | Defer = true 40 | }); 41 | } 42 | 43 | private void ShowContent() 44 | { 45 | LaraUI.Document.Body.Child( 46 | new HtmlDivElement { Class = "container mt-2"} .Child( 47 | new HtmlDivElement { Class = "jumbotron" } .Child( 48 | new HtmlImageElement 49 | { 50 | Src = ServerLauncher.ErrorAddress + ".svg", 51 | Height = "100px" 52 | }, 53 | Document.CreateElement("h1") 54 | .Wrap(x => x.Class = "display-4") 55 | .Wrap(x => x.InnerText = Title) 56 | ), 57 | Document.CreateElement("p") 58 | .Wrap(x => x.InnerText = Message) 59 | ) 60 | ); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/DiscardHandler.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal sealed class DiscardHandler : BaseHandler 13 | { 14 | private readonly Application _app; 15 | 16 | public DiscardHandler(Application app, RequestDelegate next) : base(next) 17 | { 18 | _app = app; 19 | } 20 | 21 | internal override async Task ProcessRequest(HttpContext http) 22 | { 23 | if (http.Request.Method != "POST" || http.Request.Path != "/_discard" || 24 | !DiscardParameters.TryParse(http, out var parameters) || 25 | !MiddlewareCommon.TryFindConnection(_app, http, out var connection)) return false; 26 | await Task.Delay(_app.DiscardDelay); 27 | await connection.Discard(parameters.DocumentId); 28 | await _app.ClearEmptyConnection(connection); 29 | return true; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/DiscardParameters.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Diagnostics.CodeAnalysis; 9 | using Microsoft.AspNetCore.Http; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal sealed class DiscardParameters 14 | { 15 | public Guid DocumentId { get; private set; } 16 | 17 | public static bool TryParse(HttpContext context, [NotNullWhen(true)] out DiscardParameters? parameters) 18 | { 19 | var query = context.Request.Query; 20 | return TryParse(query, out parameters); 21 | } 22 | 23 | public static bool TryParse(IQueryCollection query, [NotNullWhen(true)] out DiscardParameters? parameters) 24 | { 25 | if (MiddlewareCommon.TryGetParameter(query, "doc", out var documentText) 26 | && Guid.TryParseExact(documentText, GlobalConstants.GuidFormat, out var documentId)) 27 | { 28 | parameters = new DiscardParameters 29 | { 30 | DocumentId = documentId, 31 | }; 32 | return true; 33 | } 34 | 35 | parameters = default; 36 | return false; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/FormFile.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 12/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.IO; 9 | using System.Runtime.Serialization; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Microsoft.AspNetCore.Http; 13 | 14 | namespace Integrative.Lara 15 | { 16 | [DataContract] 17 | internal class FormFile : IFormFile 18 | { 19 | [DataMember] 20 | public string ContentType { get; set; } = string.Empty; 21 | 22 | [DataMember] 23 | public string ContentDisposition { get; set; } = string.Empty; 24 | 25 | [DataMember] 26 | public string Name { get; set; } = string.Empty; 27 | 28 | [DataMember] 29 | public string FileName { get; set; } = string.Empty; 30 | 31 | [DataMember] 32 | public string Content { get; set; } = string.Empty; 33 | 34 | private readonly HeaderDictionary _headers = new HeaderDictionary(); 35 | public IHeaderDictionary Headers => _headers; 36 | 37 | [DataMember] 38 | public long Length { get; set; } 39 | 40 | public void CopyTo(Stream target) 41 | { 42 | var bytes = GetBytes(); 43 | target.Write(bytes, 0, bytes.Length); 44 | } 45 | 46 | public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) 47 | { 48 | var bytes = GetBytes(); 49 | return target.WriteAsync(bytes, 0, bytes.Length, cancellationToken); 50 | } 51 | 52 | public Stream OpenReadStream() 53 | { 54 | var bytes = GetBytes(); 55 | return new MemoryStream(bytes); 56 | } 57 | 58 | private byte[] GetBytes() 59 | { 60 | return Convert.FromBase64String(Content); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/FormFileCollection.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 12/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.Runtime.Serialization; 11 | using Microsoft.AspNetCore.Http; 12 | 13 | namespace Integrative.Lara 14 | { 15 | [DataContract] 16 | internal class FormFileCollection : IFormFileCollection 17 | { 18 | [DataMember] 19 | public List? InnerList { get; set; } 20 | 21 | public int Count => GetCount(); 22 | 23 | private int GetCount() 24 | { 25 | return InnerList?.Count ?? 0; 26 | } 27 | 28 | public IFormFile this[string name] => GetInnerList().Find(x => x.Name == name); 29 | 30 | IFormFile IReadOnlyList.this[int index] => GetInnerList()[index]; 31 | 32 | private List GetInnerList() 33 | { 34 | return InnerList ?? throw new MissingMemberException(nameof(FormFileCollection), nameof(InnerList)); 35 | } 36 | 37 | public IFormFile GetFile(string name) 38 | { 39 | return this[name]; 40 | } 41 | 42 | public IReadOnlyList GetFiles(string name) 43 | { 44 | return GetInnerList().FindAll(x => x.Name == name); 45 | } 46 | 47 | IEnumerator IEnumerable.GetEnumerator() 48 | { 49 | return GetEnumeratorInternal(); 50 | } 51 | 52 | public IEnumerator GetEnumerator() 53 | { 54 | return GetEnumeratorInternal(); 55 | } 56 | 57 | private IEnumerator GetEnumeratorInternal() 58 | { 59 | return InnerList?.GetEnumerator() ?? GetEmptyEnumerator(); 60 | } 61 | 62 | private static IEnumerator GetEmptyEnumerator() 63 | { 64 | yield break; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/IModeController.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Hosting; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal interface IModeController 14 | { 15 | Task Start(Application app, StartServerOptions options); 16 | Connection CreateConnection(IPAddress remoteIp); 17 | double KeepAliveInterval { get; } 18 | ApplicationMode Mode { get; } 19 | bool LocalhostOnly { get; } 20 | int DiscardDelay { get; } 21 | } 22 | 23 | internal static class ModeControllerFactory 24 | { 25 | public static IModeController Create(Application app, ApplicationMode mode) 26 | { 27 | return mode == ApplicationMode.BrowserApp 28 | ? new BrowserAppController(app) 29 | : new BaseModeController(app, ApplicationMode.Default); 30 | } 31 | } 32 | 33 | internal class BaseModeController : IModeController 34 | { 35 | internal const double DefaultKeepAliveInterval 36 | = StaleConnectionsCollector.DefaultExpireInterval / 2.5; // at least 2 message attempts per expire period 37 | 38 | protected readonly Application App; 39 | 40 | public virtual int DiscardDelay => 3000; 41 | 42 | public BaseModeController(Application app, ApplicationMode mode) 43 | { 44 | App = app; 45 | Mode = mode; 46 | } 47 | 48 | public virtual double KeepAliveInterval => DefaultKeepAliveInterval; 49 | 50 | public ApplicationMode Mode { get; } 51 | 52 | public virtual bool LocalhostOnly => false; 53 | 54 | public virtual Connection CreateConnection(IPAddress remoteIp) 55 | { 56 | var connections = App.GetPublished().Connections; 57 | return connections.CreateConnection(remoteIp); 58 | } 59 | 60 | public virtual Task Start(Application app, StartServerOptions options) 61 | { 62 | return ServerLauncher.StartServer(app, options); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/KeepAliveHandler.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace Integrative.Lara 13 | { 14 | internal class KeepAliveHandler : BaseHandler 15 | { 16 | private static readonly Task _TaskFalse = Task.FromResult(false); 17 | private static readonly Task _TaskTrue = Task.FromResult(true); 18 | 19 | private const string EventPrefix = "/_keepAlive"; 20 | private const string AjaxMethod = "POST"; 21 | 22 | private readonly Application _app; 23 | 24 | public KeepAliveHandler(Application app, RequestDelegate next) : base(next) 25 | { 26 | _app = app; 27 | } 28 | 29 | internal override Task ProcessRequest(HttpContext http) 30 | { 31 | if (!IsMatch(http)) 32 | { 33 | return _TaskFalse; 34 | } 35 | TryGetDocument(http, out _); 36 | return _TaskTrue; 37 | } 38 | 39 | private static bool IsMatch(HttpContext http) 40 | { 41 | return http.Request.Path == EventPrefix 42 | && !http.WebSockets.IsWebSocketRequest 43 | && http.Request.Method == AjaxMethod; 44 | } 45 | 46 | // ReSharper disable once UnusedMethodReturnValue.Local 47 | private bool TryGetDocument(HttpContext http, [NotNullWhen(true)] out Document? document) 48 | { 49 | document = default; 50 | return MiddlewareCommon.TryGetParameter(http.Request.Query, "doc", out var text) 51 | && Guid.TryParseExact(text, GlobalConstants.GuidFormat, out var documentId) 52 | && MiddlewareCommon.TryFindConnection(_app, http, out var connection) 53 | && connection.TryGetDocument(documentId, out document); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/LaraMiddleware.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Http; 8 | using System; 9 | using System.Threading.Tasks; 10 | 11 | namespace Integrative.Lara 12 | { 13 | /// 14 | /// Lara middleware class for the ASP.NET Core framework 15 | /// 16 | public sealed class LaraMiddleware 17 | { 18 | private readonly RequestDelegate _next; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The next middleware 24 | /// Lara application 25 | /// Configuration options 26 | public LaraMiddleware(RequestDelegate next, Application app, LaraOptions options) 27 | { 28 | options = options ?? throw new ArgumentNullException(nameof(options)); 29 | next = new ClientLibraryHandler(next).Invoke; 30 | next = new PublishedItemHandler(next, app, options).Invoke; 31 | next = new DiscardHandler(app, next).Invoke; 32 | next = new KeepAliveHandler(app, next).Invoke; 33 | _next = new PostEventHandler(app, next).Invoke; 34 | } 35 | 36 | /// 37 | /// Invokes this middleware. 38 | /// 39 | /// The HttpContext. 40 | /// Task 41 | public async Task Invoke(HttpContext http) 42 | { 43 | await _next.Invoke(http); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/LocalhostFilter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 4/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using System; 10 | using System.Net; 11 | using System.Threading.Tasks; 12 | 13 | namespace Integrative.Lara 14 | { 15 | /// 16 | /// A middleware class to allow requests from localhost only. 17 | /// 18 | public sealed class LocalhostFilter 19 | { 20 | private readonly RequestDelegate _next; 21 | private readonly ILogger _logger; 22 | 23 | /// 24 | /// Constructor 25 | /// 26 | /// Next middleware delegate 27 | /// Logger 28 | public LocalhostFilter(RequestDelegate next, ILogger logger) 29 | { 30 | _next = next; 31 | _logger = logger; 32 | } 33 | 34 | /// 35 | /// Invokes this middleware 36 | /// 37 | /// The HttpContext 38 | /// Task 39 | public Task Invoke(HttpContext context) 40 | { 41 | context = context ?? throw new ArgumentNullException(nameof(context)); 42 | var remote = context.Connection.RemoteIpAddress; 43 | if (IPAddress.IsLoopback(remote)) return _next.Invoke(context); 44 | var msg = $"Forbidden request from {remote}"; 45 | _logger.LogInformation(msg); 46 | return MiddlewareCommon.SendStatusReply(context, HttpStatusCode.Forbidden, Resources.Http403); 47 | 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/NotFoundMiddleware.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Http; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | 11 | namespace Integrative.Lara 12 | { 13 | /// 14 | /// A middleware to show a simple 'not found' page 15 | /// 16 | public class NotFoundMiddleware 17 | { 18 | private readonly LaraOptions _options; 19 | private readonly Application _app; 20 | 21 | /// 22 | /// Creates an instance of NotFoundMiddleware 23 | /// 24 | /// Next middleware 25 | /// Lara application 26 | /// Configuration options 27 | // ReSharper disable once UnusedParameter.Local 28 | public NotFoundMiddleware(RequestDelegate next, Application app, LaraOptions options) 29 | { 30 | _options = options; 31 | _app = app; 32 | } 33 | 34 | /// 35 | /// Invokes this middleware 36 | /// 37 | /// The HttpContext. 38 | /// Task 39 | public Task Invoke(HttpContext context) 40 | { 41 | var page = _app.ErrorPages.GetPage(HttpStatusCode.NotFound); 42 | return page.Run(_app, context, _options); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/PostEventContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Net.WebSockets; 9 | using System.Threading.Tasks; 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace Integrative.Lara 13 | { 14 | internal class PostEventContext 15 | { 16 | public Application Application { get; set; } 17 | public HttpContext Http { get; set; } 18 | 19 | public EventParameters? Parameters { get; set; } 20 | public WebSocket? Socket { get; set; } 21 | public Connection? Connection { get; set; } 22 | public Document? Document { get; set; } 23 | public Element? Element { get; set; } 24 | 25 | public PostEventContext(Application app, HttpContext http) 26 | { 27 | Application = app; 28 | Http = http; 29 | } 30 | 31 | public bool SocketRemainsOpen() 32 | => Document != null 33 | && Parameters != null 34 | && Document.SocketRemainsOpen(Parameters.EventName); 35 | 36 | public bool IsWebSocketRequest => 37 | Http.WebSockets.IsWebSocketRequest; 38 | 39 | public virtual Task> GetSocketCompletion() 40 | { 41 | var socket = Socket ?? throw new MissingMemberException(nameof(PostEventContext), nameof(Socket)); 42 | return GetDocument().GetSocketCompletion(socket); 43 | } 44 | 45 | public Document GetDocument() 46 | { 47 | return Document ?? throw new MissingMemberException(nameof(PostEventContext), nameof(Document)); 48 | } 49 | 50 | public Connection GetConnection() 51 | { 52 | return Connection ?? throw new MissingMemberException(nameof(PostEventContext), nameof(Connection)); 53 | } 54 | 55 | public WebSocket GetSocket() 56 | { 57 | return Socket ?? throw new MissingMemberException(nameof(PostEventContext), nameof(Socket)); 58 | } 59 | 60 | public EventParameters GetParameters() 61 | { 62 | return Parameters ?? throw new MissingMemberException(nameof(PostEventContext), nameof(EventParameters)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/PublishedItemHandler.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal sealed class PublishedItemHandler : BaseHandler 13 | { 14 | private readonly LaraOptions _options; 15 | private readonly Application _app; 16 | 17 | public PublishedItemHandler(RequestDelegate next, Application app, LaraOptions options) : base(next) 18 | { 19 | _options = options; 20 | _app = app; 21 | } 22 | 23 | internal override async Task ProcessRequest(HttpContext http) 24 | { 25 | var combined = Published.CombinePathMethod(http.Request.Path, http.Request.Method); 26 | if (!_app.TryGetNode(combined, out var item)) return false; 27 | await item.Run(_app, http, _options); 28 | return true; 29 | 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/Sequencer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 10/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Collections.Generic; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara 11 | { 12 | internal class Sequencer 13 | { 14 | private static readonly Task _TaskProceed = Task.FromResult(true); 15 | private static readonly Task _TaskAbort = Task.FromResult(false); 16 | 17 | private readonly object _lock; 18 | private readonly Dictionary> _pending; 19 | private long _next; 20 | 21 | public Sequencer() 22 | { 23 | _lock = new object(); 24 | _pending = new Dictionary>(); 25 | _next = 1; 26 | } 27 | 28 | public Task WaitForTurn(long turnNumber) 29 | { 30 | if (turnNumber == 0) 31 | { 32 | return _TaskProceed; 33 | } 34 | TaskCompletionSource? completion; 35 | lock (_lock) 36 | { 37 | if (turnNumber == _next) 38 | { 39 | _next++; 40 | FlushPending(); 41 | return _TaskProceed; 42 | } 43 | 44 | if (turnNumber > _next) 45 | { 46 | completion = new TaskCompletionSource(); 47 | _pending.Add(turnNumber, completion); 48 | } 49 | else 50 | { 51 | return _TaskAbort; 52 | } 53 | } 54 | return completion.Task; 55 | } 56 | 57 | public void AbortAll() 58 | { 59 | lock (_lock) 60 | { 61 | foreach (var item in _pending.Values) 62 | { 63 | item.SetResult(false); 64 | } 65 | _pending.Clear(); 66 | } 67 | } 68 | 69 | private void FlushPending() 70 | { 71 | while (_pending.TryGetValue(_next, out var source)) 72 | { 73 | _pending.Remove(_next); 74 | source.SetResult(true); 75 | _next++; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/ServerEvent.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// The ServerEvent disposable class represents the life cycle of a server-side event. 14 | /// 15 | public sealed class ServerEvent : IDisposable 16 | { 17 | private readonly Document _document; 18 | private readonly IDisposable _access; 19 | 20 | private bool _disposed; 21 | 22 | internal ServerEvent(Document document) 23 | { 24 | _document = document; 25 | _access = document.Semaphore.UseWait(); 26 | } 27 | 28 | /// 29 | /// Flushes partial changes made to the document. 30 | /// 31 | /// Task 32 | public Task FlushPartialChanges() 33 | { 34 | VerifyNotDisposed(); 35 | return _document.ServerEventFlush(); 36 | } 37 | 38 | internal void VerifyNotDisposed() 39 | { 40 | if (_disposed) 41 | { 42 | throw new InvalidOperationException(Resources.ServerEventAlreadyDisposed); 43 | } 44 | } 45 | 46 | /// 47 | /// The dispose method flushes all pending changes in the document. 48 | /// 49 | public void Dispose() 50 | { 51 | _disposed = true; 52 | _document.ServerEventFlush().Wait(); 53 | _access.Dispose(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/StatusCodeException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Net; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Exception that returns a specific HTTP status code 14 | /// 15 | public class StatusCodeException : Exception 16 | { 17 | /// 18 | /// Status code to respond to the client 19 | /// 20 | public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.InternalServerError; 21 | 22 | /// 23 | /// Constructor 24 | /// 25 | public StatusCodeException() 26 | { 27 | } 28 | 29 | /// 30 | /// Constructor 31 | /// 32 | /// Exception message 33 | public StatusCodeException(string message) 34 | : base(message) 35 | { 36 | } 37 | 38 | /// 39 | /// Constructor 40 | /// 41 | /// Exception message 42 | /// Inner exception 43 | public StatusCodeException(string message, Exception inner) 44 | : base(message, inner) 45 | { 46 | } 47 | 48 | /// 49 | /// Constructor 50 | /// 51 | /// Status code 52 | public StatusCodeException(HttpStatusCode status) 53 | { 54 | StatusCode = status; 55 | } 56 | 57 | /// 58 | /// Constructor 59 | /// 60 | /// Status code 61 | /// Message 62 | public StatusCodeException(HttpStatusCode status, string message) 63 | : base(message) 64 | { 65 | StatusCode = status; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/LaraUI/Middleware/StatusForbiddenException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 6/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Net; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Exception that returns an HTTP status code of Forbidden 14 | /// 15 | public class StatusForbiddenException : StatusCodeException 16 | { 17 | /// 18 | /// Constructor 19 | /// 20 | public StatusForbiddenException() 21 | : base(HttpStatusCode.Forbidden) 22 | { 23 | } 24 | 25 | /// 26 | /// Constructor 27 | /// 28 | /// Exception message 29 | public StatusForbiddenException(string message) 30 | : base(HttpStatusCode.Forbidden, message) 31 | { 32 | } 33 | 34 | /// 35 | /// Constructor 36 | /// 37 | /// Exception message 38 | /// Inner exception 39 | public StatusForbiddenException(string message, Exception inner) 40 | : base(message, inner) 41 | { 42 | StatusCode = HttpStatusCode.Forbidden; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/LaraUI/NewVersionChecklist.txt: -------------------------------------------------------------------------------- 1 | new version checklist: 2 | - edit NuGet documentation with what's new in version 3 | - in the LaraClient folder run: npm run release 4 | - compile Release C# 5 | - upload XML documentation to CDN with Filezilla 6 | - pack.bat nuget package 7 | - update wiki link to to latest CDN link 8 | - upload NuGet package 9 | -------------------------------------------------------------------------------- /src/LaraUI/Reactive/BindingSubscription.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.ComponentModel; 8 | 9 | namespace Integrative.Lara 10 | { 11 | internal class BindingSubscription 12 | { 13 | public INotifyPropertyChanged Source { get; } 14 | public PropertyChangedEventHandler Handler { get; } 15 | 16 | public BindingSubscription( 17 | INotifyPropertyChanged source, 18 | PropertyChangedEventHandler handler) 19 | { 20 | Source = source; 21 | Handler = handler; 22 | Source.PropertyChanged += handler; 23 | } 24 | 25 | public void Unsubscribe() => Source.PropertyChanged -= Handler; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/LaraUI/Tools/DocumentLocal.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 9/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Integrative.Lara 11 | { 12 | /// 13 | /// Represents ambient data that is local to the current document. 14 | /// 15 | /// Value 16 | public class DocumentLocal 17 | { 18 | private readonly Dictionary _storage; 19 | 20 | /// 21 | /// Constructor 22 | /// 23 | public DocumentLocal() 24 | { 25 | _storage = new Dictionary(); 26 | } 27 | 28 | /// 29 | /// Value 30 | /// 31 | public T Value 32 | { 33 | get => GetValue(); 34 | set => SetValue(value); 35 | } 36 | 37 | private T GetValue() 38 | { 39 | var document = GetDocument(); 40 | _storage.TryGetValue(document, out var value); 41 | return value; 42 | } 43 | 44 | private void SetValue(T value) 45 | { 46 | var document = GetDocument(); 47 | if (_storage.TryGetValue(document, out var previous)) 48 | { 49 | if (LaraTools.SameValue(previous, value)) 50 | { 51 | return; 52 | } 53 | _storage.Remove(document); 54 | _storage.Add(document, value); 55 | } 56 | else 57 | { 58 | _storage.Add(document, value); 59 | document.UnloadComplete += (_, _) => _storage.Remove(document); 60 | } 61 | } 62 | 63 | private static Document GetDocument() 64 | { 65 | if (LaraUI.Page == null) 66 | { 67 | throw new InvalidOperationException(Resources.NoCurrentDocument); 68 | } 69 | return LaraUI.Page.Document; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/LaraUI/Tools/NoCurrentSessionException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara 10 | { 11 | /// 12 | /// The operation requested requires a current session and there isn't one 13 | /// 14 | public class NoCurrentSessionException : InvalidOperationException 15 | { 16 | /// 17 | /// Constructor 18 | /// 19 | /// Message 20 | public NoCurrentSessionException(string message) : base(message) 21 | { 22 | } 23 | 24 | /// 25 | /// Constructor 26 | /// 27 | /// Message 28 | /// Inner exception 29 | public NoCurrentSessionException(string message, Exception innerException) : base(message, innerException) 30 | { 31 | } 32 | 33 | /// 34 | /// Constructor 35 | /// 36 | public NoCurrentSessionException() 37 | { 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LaraUI/Tools/SemaphoreSlimExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Integrative.Lara 12 | { 13 | internal static class SemaphoreSlimExtensions 14 | { 15 | public static async Task UseWaitAsync( 16 | this SemaphoreSlim semaphore, 17 | CancellationToken cancelToken = default) 18 | { 19 | await semaphore.WaitAsync(cancelToken); 20 | return new ReleaseWrapper(semaphore); 21 | } 22 | 23 | public static IDisposable UseWait(this SemaphoreSlim semaphore, 24 | CancellationToken cancelToken = default) 25 | { 26 | semaphore.Wait(cancelToken); 27 | return new ReleaseWrapper(semaphore); 28 | } 29 | 30 | private class ReleaseWrapper : IDisposable 31 | { 32 | private readonly SemaphoreSlim _semaphore; 33 | 34 | private bool _disposed; 35 | 36 | public ReleaseWrapper(SemaphoreSlim semaphore) 37 | { 38 | _semaphore = semaphore; 39 | } 40 | 41 | public void Dispose() 42 | { 43 | if (_disposed) return; 44 | _disposed = true; 45 | _semaphore.Release(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LaraUI/Tools/ServerLauncher.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Builder; 10 | using Microsoft.AspNetCore.Hosting; 11 | 12 | namespace Integrative.Lara 13 | { 14 | internal static class ServerLauncher 15 | { 16 | public const string ErrorAddress = "/Error"; 17 | 18 | public static async Task StartServer(Application app, StartServerOptions options) 19 | { 20 | var host = CreateBrowserHost(app, options); 21 | await host.StartAsync(CancellationToken.None); 22 | return host; 23 | } 24 | 25 | private static IWebHost CreateBrowserHost(Application laraApp, StartServerOptions options) 26 | { 27 | var address = options.IPAddress; 28 | var port = options.Port; 29 | var builder = new WebHostBuilder() 30 | .UseKestrel(kestrel => kestrel.Listen(address, port)) 31 | .Configure(app => 32 | { 33 | ConfigureApp(app, laraApp, options); 34 | }); 35 | options.AdditionalConfiguration?.Invoke(builder); 36 | return builder.Build(); 37 | } 38 | 39 | private static void ConfigureApp(IApplicationBuilder app, Application laraApp, StartServerOptions options) 40 | { 41 | ConfigureExceptions(app, laraApp, options); 42 | app.UseLara(laraApp, options); 43 | } 44 | 45 | internal static void ConfigureExceptions(IApplicationBuilder app, Application laraApp, StartServerOptions options) 46 | { 47 | if (options.ShowExceptions) 48 | { 49 | app.UseDeveloperExceptionPage(); 50 | } 51 | else 52 | { 53 | app.UseExceptionHandler(ErrorAddress); 54 | laraApp.ErrorPages.PublishErrorPage(); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/LaraUI/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ 7 | "**.csproj" 8 | ], 9 | "src": "C:\\Users\\Pablo\\OneDrive\\2019\\LaraUI\\src\\LaraUI" 10 | } 11 | ], 12 | "dest": "api", 13 | "disableGitFeatures": false, 14 | "disableDefaultFilter": false 15 | } 16 | ], 17 | "build": { 18 | "content": [ 19 | { 20 | "files": [ 21 | "api/**.yml", 22 | "api/index.md" 23 | ] 24 | }, 25 | { 26 | "files": [ 27 | "articles/**.md", 28 | "articles/**/toc.yml", 29 | "toc.yml", 30 | "*.md" 31 | ] 32 | } 33 | ], 34 | "resource": [ 35 | { 36 | "files": [ 37 | "images/**" 38 | ] 39 | } 40 | ], 41 | "overwrite": [ 42 | { 43 | "files": [ 44 | "apidoc/**.md" 45 | ], 46 | "exclude": [ 47 | "obj/**", 48 | "_site/**" 49 | ] 50 | } 51 | ], 52 | "dest": "_site", 53 | "globalMetadataFiles": [], 54 | "fileMetadataFiles": [], 55 | "template": [ 56 | "default" 57 | ], 58 | "postProcessors": [], 59 | "markdownEngineName": "markdig", 60 | "noLangKeyword": false, 61 | "keepFileLink": false, 62 | "cleanupCacheHistory": false, 63 | "disableGitFeatures": false 64 | } 65 | } -------------------------------------------------------------------------------- /src/LaraUI/pack.bat: -------------------------------------------------------------------------------- 1 | dotnet pack -c RELEASE -------------------------------------------------------------------------------- /src/SampleProject/Common/SampleAppBootstrap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | 9 | namespace SampleProject.Common 10 | { 11 | internal static class SampleAppBootstrap 12 | { 13 | public static void AppendTo(Element head) 14 | { 15 | head.AppendChild(new HtmlLinkElement 16 | { 17 | Rel = "stylesheet", 18 | HRef = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" 19 | }); 20 | head.AppendChild(new HtmlScriptElement 21 | { 22 | Src = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js", 23 | Defer = true 24 | }); 25 | head.AppendChild(new HtmlScriptElement 26 | { 27 | Src = "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js", 28 | Defer = true 29 | }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SampleProject/Common/Tools.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 9/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | using System; 9 | using System.Reflection; 10 | 11 | namespace SampleProject.Common 12 | { 13 | internal class Tools 14 | { 15 | public static byte[] LoadEmbeddedResource(Assembly assembly, string resourceName) 16 | { 17 | using var stream = assembly.GetManifestResourceStream(resourceName); 18 | if (stream == null) throw new InvalidOperationException($"Resource not found: {resourceName}"); 19 | var bytes = new byte[stream.Length]; 20 | stream.Read(bytes, 0, bytes.Length); 21 | return bytes; 22 | } 23 | 24 | public static string GetSpinnerHtml(string message) 25 | { 26 | var div = new HtmlDivElement 27 | { 28 | Class = "d-flex justify-content-center", 29 | Children = new Node[] 30 | { 31 | new HtmlDivElement 32 | { 33 | Class = "spinner-border", 34 | }, 35 | new HtmlDivElement 36 | { 37 | Class = "ml-2", 38 | InnerText = message 39 | } 40 | } 41 | }; 42 | return div.GetHtml(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/SampleProject/Components/CounterSample.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 1/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | 9 | namespace SampleProject.Components 10 | { 11 | internal class CounterSample : WebComponent 12 | { 13 | private int _counter = 5; 14 | private int Counter { get => _counter; set => SetProperty(ref _counter, value); } 15 | 16 | private static int TextToInt(string? value) 17 | { 18 | return int.TryParse(value, out var result) ? result : 0; 19 | } 20 | 21 | public CounterSample() 22 | { 23 | ShadowRoot.Children = new Node[] 24 | { 25 | new HtmlDivElement 26 | { 27 | Class = "form-row", 28 | Children = new Node[] 29 | { 30 | new HtmlDivElement 31 | { 32 | Class = "form-group col-md-2", 33 | Children = new Node[] 34 | { 35 | new HtmlInputElement 36 | { 37 | Type = "number", 38 | Class = "form-control" 39 | } 40 | .Bind(this, x => x.Value = Counter.ToString()) 41 | .BindBack(x => Counter = TextToInt(x.Value)) 42 | } 43 | }, 44 | new HtmlDivElement 45 | { 46 | Class = "form-group col-md-1", 47 | Children = new Node[] 48 | { 49 | new HtmlButtonElement 50 | { 51 | InnerText = "Increase", 52 | Class = "btn btn-primary" 53 | } 54 | .Event("click", () => Counter++) 55 | } 56 | } 57 | } 58 | } 59 | }; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SampleProject/Components/LockingSample.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | using SampleProject.Common; 9 | using System.Threading.Tasks; 10 | 11 | namespace SampleProject.Components 12 | { 13 | internal class LockingSample : WebComponent 14 | { 15 | public LockingSample() 16 | { 17 | ShadowRoot.Children = new Node[] 18 | { 19 | new HtmlDivElement 20 | { 21 | Class = "form-row", 22 | Children = new Node[] 23 | { 24 | new HtmlButtonElement 25 | { 26 | Class = "btn btn-primary my-2", 27 | InnerText = "Action that blocks the UI" 28 | } 29 | .Event(new EventSettings 30 | { 31 | EventName = "click", 32 | Handler = () => Task.Delay(1000), 33 | BlockOptions = new BlockOptions 34 | { 35 | ShowHtmlMessage = Tools.GetSpinnerHtml("Brewing coffee...") 36 | } 37 | }) 38 | } 39 | }, 40 | }; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SampleProject/Components/MultiselectSample.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | 9 | namespace SampleProject.Components 10 | { 11 | internal class MultiselectSample : WebComponent 12 | { 13 | public MultiselectSample() 14 | { 15 | var combo = new HtmlSelectElement 16 | { 17 | Class = "form-control", 18 | Multiple = true 19 | }; 20 | combo.AddOption("N", "North"); 21 | combo.AddOption("E", "East"); 22 | combo.AddOption("S", "South"); 23 | combo.AddOption("W", "West"); 24 | var toggle = new HtmlButtonElement 25 | { 26 | Class = "btn btn-primary", 27 | InnerText = "Toggle" 28 | }; 29 | toggle.On("click", () => 30 | { 31 | foreach (var child in combo.Children) 32 | { 33 | if (child is not HtmlOptionElement option) continue; 34 | option.Selected = !option.Selected; 35 | } 36 | }); 37 | ShadowRoot.Children = new Node[] 38 | { 39 | new HtmlDivElement 40 | { 41 | Class = "form-row", 42 | Children = new Node[] 43 | { 44 | new HtmlDivElement 45 | { 46 | Class = "form-group col-md-2", 47 | Children = new Node[] { combo } 48 | }, 49 | new HtmlDivElement 50 | { 51 | Class = "form-group col-md-1", 52 | Children = new Node[] { toggle } 53 | } 54 | } 55 | } 56 | }; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/SampleProject/Components/SelectSample.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | 9 | namespace SampleProject.Components 10 | { 11 | internal class SelectSample : WebComponent 12 | { 13 | public int Test { get; init; } 14 | 15 | public SelectSample() 16 | { 17 | ShadowRoot.Child( 18 | new HtmlDivElement { Class = "form-row" }.Child( 19 | new HtmlDivElement { Class = "form-group col-md-2" }.Child( 20 | new WeekdayCombo().Extract(out var combo) 21 | ), 22 | new HtmlDivElement { Class = "form-group col-md-1"}.Child( 23 | new HtmlButtonElement 24 | { InnerText = "Advance", Class ="btn btn-primary" } 25 | .Event("click", () => combo.NextDay()) 26 | ) 27 | )); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SampleProject/Components/WeekdayCombo.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | 9 | namespace SampleProject.Components 10 | { 11 | public class WeekdayCombo : WebComponent 12 | { 13 | private static readonly string[] _WeekDays 14 | = { "Monday", "Tuesday", "Wednesday", "Thursday", 15 | "Friday", "Saturday", "Sunday" }; 16 | 17 | private int _weekday; 18 | private int Weekday { get => _weekday; set => SetProperty(ref _weekday, value); } 19 | 20 | public WeekdayCombo() 21 | { 22 | var combo = new HtmlSelectElement { Class = "form-control", Id = "MyWeek" }; 23 | for (var index = 0; index < _WeekDays.Length; index++) 24 | { 25 | combo.AddOption(index.ToString(), _WeekDays[index]); 26 | } 27 | combo.Bind(this, _ => combo.Value = Weekday.ToString()); 28 | combo.BindBack(_ => Weekday = int.Parse(combo.Value ?? "")); 29 | ShadowRoot.Children = new[] { combo }; 30 | } 31 | 32 | public void NextDay() 33 | => Weekday = (Weekday + 1) % _WeekDays.Length; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/SampleProject/LaraSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | x64;AnyCPU 6 | win-x64;osx-x64;linux-x64 7 | WinExe 8 | 9 | 10 | latest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/SampleProject/Main/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | using SampleProject.Pages; 9 | using System; 10 | using System.Threading.Tasks; 11 | 12 | namespace SampleProject.Main 13 | { 14 | internal static class Program 15 | { 16 | private static async Task Main() 17 | { 18 | // create application 19 | using var app = new Application(); 20 | KitchenSinkPage.PublishMugImage(app); 21 | app.PublishPage("/", () => new KitchenSinkPage()); 22 | app.PublishPage("/upload", () => new UploadFilePage()); 23 | app.PublishPage("/server", () => new ServerEventsPage()); 24 | 25 | // start application 26 | await app.Start(new StartServerOptions { Port = 8182 }); 27 | Console.WriteLine("Listening on http://localhost:8182/"); 28 | LaraUI.LaunchBrowser("http://localhost:8182"); 29 | 30 | // wait for shutdown 31 | await app.WaitForShutdown(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SampleProject/Pages/KitchenSinkPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020-2021 Integrative Software LLC 3 | Created: 12/2020 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | using SampleProject.Components; 9 | using SampleProject.Common; 10 | using System.Threading.Tasks; 11 | 12 | namespace SampleProject.Pages 13 | { 14 | internal class KitchenSinkPage : IPage 15 | { 16 | public Task OnGet() 17 | { 18 | var document = LaraUI.Page.Document; 19 | 20 | // This sample application loads the CSS library 'Bootstrap' 21 | SampleAppBootstrap.AppendTo(document.Head); 22 | 23 | document.Body.AppendChild(new KitchenSinkComponent()); 24 | 25 | return Task.CompletedTask; 26 | } 27 | 28 | public static void PublishMugImage(Application app) 29 | { 30 | var assembly = typeof(KitchenSinkPage).Assembly; 31 | var bytes = Tools.LoadEmbeddedResource(assembly, "SampleProject.Assets.Coffee.svg"); 32 | app.PublishFile("/Coffee.svg", new StaticContent(bytes, "image/svg+xml")); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/SampleProject/Pages/ServerEventsPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 8/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | using Integrative.Lara; 9 | 10 | namespace SampleProject.Pages 11 | { 12 | internal class ServerEventsPage : IPage 13 | { 14 | private readonly HtmlSpanElement _span; 15 | 16 | public ServerEventsPage() 17 | { 18 | _span = new HtmlSpanElement(); 19 | _span.AppendText("waiting for server event..."); 20 | } 21 | 22 | public Task OnGet() 23 | { 24 | LaraUI.Page.JSBridge.ServerEventsOn(); 25 | LaraUI.Page.Document.Body.AppendChild(_span); 26 | Task.Run(DelayedTask); 27 | return Task.CompletedTask; 28 | } 29 | 30 | private async void DelayedTask() 31 | { 32 | await Task.Delay(4000); 33 | using var access = LaraUI.Document.StartServerEvent(); 34 | _span.ClearChildren(); 35 | _span.AppendText("server event executed"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SampleProject/Pages/UploadFilePage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara; 8 | using SampleProject.Components; 9 | using System.Threading.Tasks; 10 | 11 | namespace SampleProject.Pages 12 | { 13 | internal class UploadFilePage : IPage 14 | { 15 | public Task OnGet() 16 | { 17 | LaraUI.Document.Body.AppendChild(new UploadSample()); 18 | return Task.CompletedTask; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SampleProject/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SampleProject": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/SampleProject/SampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | x64;AnyCPU 6 | win-x64;osx-x64;linux-x64 7 | Exe 8 | 9 | 10 | latest 11 | enable 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Tests/Assets/Compressible.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/integrativesoft/lara/67fb43a3e0d1b5f7964e46b9552ad909dcf9de7a/src/Tests/Assets/Compressible.bmp -------------------------------------------------------------------------------- /src/Tests/Assets/pexels-photo-248673.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/integrativesoft/lara/67fb43a3e0d1b5f7964e46b9552ad909dcf9de7a/src/Tests/Assets/pexels-photo-248673.jpeg -------------------------------------------------------------------------------- /src/Tests/DOM/ClassEditorTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Xunit; 8 | 9 | namespace Integrative.Lara.Tests.DOM 10 | { 11 | public class ClassEditorTesting 12 | { 13 | [Fact] 14 | public void HasEmptyClassTrue() 15 | { 16 | Assert.True(ClassEditor.HasClass("", "")); 17 | } 18 | 19 | [Fact] 20 | public void EmptyClassFalse() 21 | { 22 | Assert.False(ClassEditor.HasClass("", "lele")); 23 | } 24 | 25 | [Fact] 26 | public void HasClassTrue() 27 | { 28 | Assert.True(ClassEditor.HasClass("aaa", "aaa")); 29 | Assert.True(ClassEditor.HasClass("aaa b", "aaa")); 30 | Assert.True(ClassEditor.HasClass("b aaa", "aaa")); 31 | Assert.True(ClassEditor.HasClass(" aaa", "aaa")); 32 | Assert.True(ClassEditor.HasClass("cc aaa ddd", "aaa")); 33 | } 34 | 35 | [Fact] 36 | public void RemoveClass() 37 | { 38 | Assert.Equal("lala", ClassEditor.RemoveClass("lala", "")); 39 | Assert.Equal("", ClassEditor.RemoveClass("lala", "lala")); 40 | Assert.Equal("", ClassEditor.RemoveClass(" lala ", "lala")); 41 | Assert.Equal("", ClassEditor.RemoveClass("lala", " lala ")); 42 | Assert.Equal("blue", ClassEditor.RemoveClass("lala blue", "lala")); 43 | Assert.Equal("blue", ClassEditor.RemoveClass("blue lala", "lala")); 44 | Assert.Equal("red blue", ClassEditor.RemoveClass("red lala blue", "lala")); 45 | Assert.Equal("orange", ClassEditor.RemoveClass("orange", "lala")); 46 | } 47 | 48 | [Fact] 49 | public void AddClass() 50 | { 51 | Assert.Equal("lala", ClassEditor.AddClass("lala", "lala")); 52 | Assert.Equal("lala", ClassEditor.AddClass(" ", "lala")); 53 | Assert.Equal(" red lala", ClassEditor.AddClass(" red", "lala")); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Tests/Delta/LocatorTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara.Tests.Middleware; 8 | using Xunit; 9 | 10 | namespace Integrative.Lara.Tests.Delta 11 | { 12 | public class LocatorTesting : DummyContextTesting 13 | { 14 | [Fact] 15 | public void LocateElementWithId() 16 | { 17 | var x = Element.Create("span", "x"); 18 | var locator = NodeLocator.FromNode(x); 19 | Assert.Equal(x.Id, locator.StartingId); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Tests/Main/ButtonCounterPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System.Threading.Tasks; 8 | 9 | namespace Integrative.Lara.Tests.Main 10 | { 11 | internal class ButtonCounterPage : IPage 12 | { 13 | private const string ButtonId = "MyCounterButton"; 14 | 15 | private readonly bool _useSockets; 16 | 17 | public ButtonCounterPage(bool useSockets) 18 | { 19 | _useSockets = useSockets; 20 | } 21 | 22 | private int _counter; 23 | 24 | public Task OnGet() 25 | { 26 | var document = LaraUI.Page.Document; 27 | var span = Element.Create("span"); 28 | var text = new TextNode("Click me"); 29 | var button = Element.Create("button", ButtonId); 30 | button.AppendChild(text); 31 | document.Body.AppendChild(span); 32 | document.Body.AppendChild(button); 33 | button.On(new EventSettings 34 | { 35 | EventName = "click", 36 | LongRunning = _useSockets, 37 | Handler = () => 38 | { 39 | _counter++; 40 | text.Data = $"Clicked {_counter} times"; 41 | return Task.CompletedTask; 42 | } 43 | }); 44 | return Task.CompletedTask; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Tests/Main/MyPage.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | using System.Threading.Tasks; 9 | 10 | namespace Integrative.Lara.Tests.Main 11 | { 12 | internal class MyPage : IPage, IDisposable 13 | { 14 | public bool Disposed { get; private set; } 15 | 16 | public void Dispose() 17 | { 18 | Disposed = true; 19 | } 20 | 21 | public Task OnGet() 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Tests/Main/StaleTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 5/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Integrative.Lara.Tests.Middleware; 8 | using System; 9 | using System.Net; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace Integrative.Lara.Tests.Main 14 | { 15 | public class StaleTesting : DummyContextTesting 16 | { 17 | [Fact] 18 | public async void CleanupLeavesUnexpiredDocument() 19 | { 20 | var connections = new Connections(); 21 | var cnx = connections.CreateConnection(IPAddress.Loopback); 22 | var doc1 = cnx.CreateDocument(new MyPage(), BaseModeController.DefaultKeepAliveInterval); 23 | var doc2 = cnx.CreateDocument(new MyPage(), BaseModeController.DefaultKeepAliveInterval); 24 | doc2.ModifyLastUtcForTesting(DateTime.UtcNow.AddHours(-10)); 25 | await Task.Delay(200); 26 | using (var collector = new StaleConnectionsCollector(connections)) 27 | { 28 | await collector.CleanupExpiredHandler(); 29 | } 30 | Assert.True(cnx.TryGetDocument(doc1.VirtualId, out _)); 31 | Assert.False(cnx.TryGetDocument(doc2.VirtualId, out _)); 32 | } 33 | 34 | [Fact] 35 | public async void EmptyConnectionGetsCollected() 36 | { 37 | var connections = new Connections(); 38 | var cnx = connections.CreateConnection(IPAddress.Loopback); 39 | using (var collector = new StaleConnectionsCollector(connections)) 40 | { 41 | await collector.CleanupExpiredHandler(); 42 | } 43 | Assert.False(connections.TryGetConnection(cnx.Id, out _)); 44 | } 45 | 46 | [Fact] 47 | public void TimerInterval() 48 | { 49 | using var x = new Connections 50 | { 51 | StaleCollectionInterval = 100, 52 | StaleExpirationInterval = 200 53 | }; 54 | Assert.Equal(100, x.StaleCollectionInterval); 55 | Assert.Equal(200, x.StaleExpirationInterval); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Tests/Middleware/DummyContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Microsoft.AspNetCore.Http; 8 | using Moq; 9 | using System; 10 | using System.Diagnostics.CodeAnalysis; 11 | using System.Net; 12 | 13 | namespace Integrative.Lara.Tests.Middleware 14 | { 15 | internal class DummyContext : BaseContext, IPageContext, IWebServiceContext 16 | { 17 | private DummyContext(Application app, Mock http) 18 | : base(app, http.Object) 19 | { 20 | var request = new Mock(); 21 | http.Setup(x => x.Request).Returns(request.Object); 22 | request.Setup(x => x.Path).Returns("/abc"); 23 | var guid = Connections.CreateCryptographicallySecureGuid(); 24 | var cnx = new Connection(guid, IPAddress.Loopback); 25 | Session = new Session(cnx); 26 | JSBridge = _bridge.Object; 27 | } 28 | 29 | public Document Document => throw new NotImplementedException(); 30 | 31 | private readonly Mock _bridge = new Mock(); 32 | 33 | public IJsBridge JSBridge { get; set; } 34 | 35 | public INavigation Navigation => throw new NotImplementedException(); 36 | 37 | public Session Session { get; } 38 | 39 | public string RequestBody { get; set; } = string.Empty; 40 | 41 | public HttpStatusCode StatusCode { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 42 | 43 | public static DummyContext Create() 44 | { 45 | var app = new Application(); 46 | var http = new Mock(); 47 | return new DummyContext(app, http); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | Application.Dispose(); 53 | } 54 | 55 | public bool TryGetSession([NotNullWhen(true)] out Session? session) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Tests/Middleware/DummyContextTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 11/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using System; 8 | 9 | namespace Integrative.Lara.Tests.Middleware 10 | { 11 | public class DummyContextTesting : IDisposable 12 | { 13 | internal readonly DummyContext Context = DummyContext.Create(); 14 | 15 | public void Dispose() 16 | { 17 | Context.Dispose(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Tests/Middleware/ErrorPagesTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-2021 Integrative Software LLC 3 | Created: 9/2019 4 | Author: Pablo Carbonell 5 | */ 6 | 7 | using Xunit; 8 | 9 | namespace Integrative.Lara.Tests.Middleware 10 | { 11 | public class ErrorPagesTesting : DummyContextTesting 12 | { 13 | [Fact] 14 | public void DefaultNotFoundRuns() 15 | { 16 | var pages = new ErrorPages(Context.Application.GetPublished()); 17 | var page = pages.GetPage(System.Net.HttpStatusCode.NotFound); 18 | Assert.NotNull(page); 19 | } 20 | 21 | [Fact] 22 | public void DefaultServerErrorRuns() 23 | { 24 | var pages = new ErrorPages(Context.Application.GetPublished()); 25 | var found = pages.TryGetPage(System.Net.HttpStatusCode.InternalServerError, out var page); 26 | Assert.True(found); 27 | Assert.NotNull(page); 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | Integrative.Lara.Tests 9 | 10 | x64;AnyCPU 11 | 12 | latest 13 | enable 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------