├── .gitignore ├── .gitmodules ├── LICENSE.md ├── README.md └── src ├── .dockerignore ├── Marco.Forms.UI ├── Marco.Forms.UI.Client │ ├── Components │ │ ├── Actions │ │ │ ├── BlockAction.cs │ │ │ ├── BlockActions.cs │ │ │ ├── ChangeSectionAction.cs │ │ │ ├── DebugBlockAction.cs │ │ │ ├── DeleteBlockAction.cs │ │ │ ├── EditBlockAction.cs │ │ │ ├── InsertBlockAction.cs │ │ │ ├── MoveAction.cs │ │ │ ├── MoveDownAction.cs │ │ │ └── MoveUpAction.cs │ │ ├── BlockActionMessage.cs │ │ ├── BlockController.razor │ │ ├── BlockController.razor.cs │ │ ├── BlockLayout.cs │ │ ├── BlockLayoutExtensions.cs │ │ ├── BlockLayoutType.cs │ │ ├── Blocks │ │ │ ├── BlockComponentBase.cs │ │ │ ├── BlockDefinitionGroupTitle.razor │ │ │ ├── BlockDefinitionList.razor │ │ │ ├── BlockDefinitionService.cs │ │ │ ├── BlockEditContainer.razor │ │ │ ├── BlockRenderer2.cs │ │ │ ├── BlockWindow.razor │ │ │ ├── ContainerBlockView.cs │ │ │ ├── ContentBlock.cs │ │ │ ├── IBlazorBlock.cs │ │ │ ├── LinkBlock.cs │ │ │ ├── ListBlock.cs │ │ │ ├── SectionBlock.razor │ │ │ ├── SectionBlock.razor.cs │ │ │ ├── SectionViewBlock.razor │ │ │ ├── SectionViewBlock.razor.cs │ │ │ ├── StatePickerBlock.cs │ │ │ ├── TailwindBackgroundColorMapper.cs │ │ │ └── TailwindTextColorTransformer.cs │ │ ├── Controls │ │ │ ├── BlockContextMenu.razor │ │ │ ├── BlockSelector.razor │ │ │ ├── ColorPicker.razor │ │ │ ├── DockableContent.razor │ │ │ ├── FloatingContainer.razor │ │ │ └── FloatingContainerContent.razor │ │ ├── CssUnit.cs │ │ ├── DomRect.cs │ │ ├── ExampleWizard.razor │ │ ├── FixedLayoutOptions.cs │ │ ├── Forms │ │ │ ├── CheckboxBlock.cs │ │ │ ├── CheckboxField.razor │ │ │ ├── FieldSchema.cs │ │ │ ├── FieldType.cs │ │ │ ├── FieldValue.cs │ │ │ ├── FieldValueType.cs │ │ │ ├── Form.cs │ │ │ ├── FormAdminPage.razor │ │ │ ├── FormBuilderContainer.razor │ │ │ ├── FormCanvas.razor │ │ │ ├── FormCard.razor │ │ │ ├── FormDto.cs │ │ │ ├── FormEditor.razor │ │ │ ├── FormField.cs │ │ │ ├── FormFieldEditor.razor │ │ │ ├── FormFieldOption.cs │ │ │ ├── FormFieldRenderer.razor │ │ │ ├── FormGPT.razor │ │ │ ├── FormPreviewDialog.razor │ │ │ ├── FormRenderer.razor │ │ │ ├── FormResponse.cs │ │ │ ├── FormResponsePage.razor │ │ │ ├── FormSchema.cs │ │ │ ├── FormSection.cs │ │ │ ├── FormStatus.cs │ │ │ ├── FormStepByStepWizard.razor │ │ │ ├── FormStore.cs │ │ │ ├── FormSubmission.cs │ │ │ ├── FormsClient.cs │ │ │ ├── IFormSuggestionService.cs │ │ │ ├── MarcoForm.razor │ │ │ ├── MoreButton.razor │ │ │ ├── NewFormComponent.razor │ │ │ ├── RadioBlock.cs │ │ │ ├── SelectBlock.cs │ │ │ ├── ServerFormAiClient.cs │ │ │ ├── ShareLink.cs │ │ │ └── SuggestionPreview.razor │ │ ├── GridAutoColumns.cs │ │ ├── GridAutoFlow.cs │ │ ├── GridAutoRows.cs │ │ ├── GridLayoutOptions.cs │ │ ├── IPositioner.cs │ │ ├── Layers.razor │ │ ├── MarcoCanvas.razor │ │ ├── MarcoCanvas.razor.cs │ │ ├── MarcoCanvas.razor.js │ │ ├── MarcoCanvasState.cs │ │ ├── MouseEventExtensions.cs │ │ ├── MoveBlockDialog.razor │ │ ├── Node.cs │ │ ├── NodeType.cs │ │ ├── PageBlock.cs │ │ ├── Primitives │ │ │ ├── FontWeights.cs │ │ │ ├── Selection.cs │ │ │ └── TailwindColor.cs │ │ ├── Properties │ │ │ ├── BoxPosition.cs │ │ │ ├── BoxPositionExtensions.cs │ │ │ ├── GeneralProperties.razor │ │ │ ├── LayoutProperties.razor │ │ │ ├── PositionAnchor.cs │ │ │ ├── SectionProperties.razor │ │ │ └── SpacingAdjuster.razor │ │ ├── ResizableContainer.razor │ │ ├── Samples │ │ │ ├── EmbedFormGPT.razor │ │ │ ├── FormLoader.razor │ │ │ ├── MyDataCapturePage.razor │ │ │ └── _Imports.razor │ │ ├── ScreenPositioner.cs │ │ ├── SelectionExtensions.cs │ │ ├── SelectionRange.cs │ │ ├── Shared │ │ │ ├── AppBar.razor │ │ │ ├── DefaultAppBar.razor │ │ │ ├── DefaultErrorBoundary.cs │ │ │ ├── Error.razor │ │ │ ├── FullPageComponent.razor │ │ │ ├── FullPageLayout.razor │ │ │ ├── HoverToggle.razor │ │ │ ├── Overlay.cs │ │ │ ├── PopupMenu.cs │ │ │ ├── Pressable.cs │ │ │ ├── ProfileButton.razor │ │ │ ├── RedirectToLogin.razor │ │ │ ├── TopToolbar.razor │ │ │ └── TrackHover.cs │ │ ├── Spacing.cs │ │ ├── TextAlign.cs │ │ ├── Trees │ │ │ ├── TreeItemType.cs │ │ │ ├── TreeView.razor │ │ │ └── TreeViewItem.cs │ │ ├── Wizard.razor │ │ ├── WizardStep.razor │ │ └── WizardStepDefinition.cs │ ├── Dockerfile │ ├── Marco.Forms.UI.Client.csproj │ ├── Models │ │ ├── AccountDetails.cs │ │ ├── AccountStatus.cs │ │ ├── BlockData.cs │ │ ├── BlockDefaults.cs │ │ ├── BlockDefinition.cs │ │ ├── BlockFontFamily.cs │ │ ├── BlockFormatting.cs │ │ ├── BlockLayout.cs │ │ ├── BlockLayoutExtensions.cs │ │ ├── BlockLayoutType.cs │ │ ├── BlockTypes.cs │ │ ├── Border.cs │ │ ├── ContentBlockData.cs │ │ ├── CssUnitExtensions.cs │ │ ├── FixedLayoutOptions.cs │ │ ├── FormattingDefinitionExtensions.cs │ │ ├── GridAutoColumns.cs │ │ ├── GridAutoFlow.cs │ │ ├── GridAutoRows.cs │ │ ├── GridLayoutOptions.cs │ │ ├── LinkBlockData.cs │ │ ├── ListBlockData.cs │ │ ├── ListItem.cs │ │ ├── ListType.cs │ │ ├── NavigationBlockData.cs │ │ ├── RootBlock.cs │ │ ├── SectionBlockData.cs │ │ ├── ServerClient.cs │ │ ├── SiteOptions.cs │ │ └── Uuid.cs │ ├── Program.cs │ └── _Imports.razor └── Marco.Forms.UI │ ├── Components │ ├── App.razor │ ├── Layout │ │ ├── MainLayout.razor │ │ └── MainLayout.razor.css │ ├── Pages │ │ └── Error.razor │ ├── Routes.razor │ └── _Imports.razor │ ├── Dockerfile │ ├── Marco.Forms.UI.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ ├── app.css │ └── css │ └── inter │ ├── Inter-Black.woff2 │ ├── Inter-BlackItalic.woff2 │ ├── Inter-Bold.woff2 │ ├── Inter-BoldItalic.woff2 │ ├── Inter-ExtraBold.woff2 │ ├── Inter-ExtraBoldItalic.woff2 │ ├── Inter-ExtraLight.woff2 │ ├── Inter-ExtraLightItalic.woff2 │ ├── Inter-Italic.woff2 │ ├── Inter-Light.woff2 │ ├── Inter-LightItalic.woff2 │ ├── Inter-Medium.woff2 │ ├── Inter-MediumItalic.woff2 │ ├── Inter-Regular.woff2 │ ├── Inter-SemiBold.woff2 │ ├── Inter-SemiBoldItalic.woff2 │ ├── Inter-Thin.woff2 │ ├── Inter-ThinItalic.woff2 │ ├── InterDisplay-Black.woff2 │ ├── InterDisplay-BlackItalic.woff2 │ ├── InterDisplay-Bold.woff2 │ ├── InterDisplay-BoldItalic.woff2 │ ├── InterDisplay-ExtraBold.woff2 │ ├── InterDisplay-ExtraBoldItalic.woff2 │ ├── InterDisplay-ExtraLight.woff2 │ ├── InterDisplay-ExtraLightItalic.woff2 │ ├── InterDisplay-Italic.woff2 │ ├── InterDisplay-Light.woff2 │ ├── InterDisplay-LightItalic.woff2 │ ├── InterDisplay-Medium.woff2 │ ├── InterDisplay-MediumItalic.woff2 │ ├── InterDisplay-Regular.woff2 │ ├── InterDisplay-SemiBold.woff2 │ ├── InterDisplay-SemiBoldItalic.woff2 │ ├── InterDisplay-Thin.woff2 │ ├── InterDisplay-ThinItalic.woff2 │ ├── InterVariable-Italic.woff2 │ ├── InterVariable.woff2 │ └── inter.css ├── Marco.sln └── compose.yaml /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components"] 2 | path = components 3 | url = https://github.com/pureblazor/components.git 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Functional Source License, Version 1.1, ALv2 Future License 2 | 3 | ## Abbreviation 4 | 5 | FSL-1.1-ALv2 6 | 7 | ## Notice 8 | 9 | Copyright 2025 Codefrog, Inc 10 | 11 | ## Terms and Conditions 12 | 13 | ### Licensor ("We") 14 | 15 | The party offering the Software under these Terms and Conditions. 16 | 17 | ### The Software 18 | 19 | The "Software" is each version of the software that we make available under 20 | these Terms and Conditions, as indicated by our inclusion of these Terms and 21 | Conditions with the Software. 22 | 23 | ### License Grant 24 | 25 | Subject to your compliance with this License Grant and the Patents, 26 | Redistribution and Trademark clauses below, we hereby grant you the right to 27 | use, copy, modify, create derivative works, publicly perform, publicly display 28 | and redistribute the Software for any Permitted Purpose identified below. 29 | 30 | ### Permitted Purpose 31 | 32 | A Permitted Purpose is any purpose other than a Competing Use. A Competing Use 33 | means making the Software available to others in a commercial product or 34 | service that: 35 | 36 | 1. substitutes for the Software; 37 | 38 | 2. substitutes for any other product or service we offer using the Software 39 | that exists as of the date we make the Software available; or 40 | 41 | 3. offers the same or substantially similar functionality as the Software. 42 | 43 | Permitted Purposes specifically include using the Software: 44 | 45 | 1. for your internal use and access; 46 | 47 | 2. for non-commercial education; 48 | 49 | 3. for non-commercial research; and 50 | 51 | 4. in connection with professional services that you provide to a licensee 52 | using the Software in accordance with these Terms and Conditions. 53 | 54 | ### Patents 55 | 56 | To the extent your use for a Permitted Purpose would necessarily infringe our 57 | patents, the license grant above includes a license under our patents. If you 58 | make a claim against any party that the Software infringes or contributes to 59 | the infringement of any patent, then your patent license to the Software ends 60 | immediately. 61 | 62 | ### Redistribution 63 | 64 | The Terms and Conditions apply to all copies, modifications and derivatives of 65 | the Software. 66 | 67 | If you redistribute any copies, modifications or derivatives of the Software, 68 | you must include a copy of or a link to these Terms and Conditions and not 69 | remove any copyright notices provided in or with the Software. 70 | 71 | ### Disclaimer 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR 75 | PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT. 76 | 77 | IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE 78 | SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, 79 | EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE. 80 | 81 | ### Trademarks 82 | 83 | Except for displaying the License Details and identifying us as the origin of 84 | the Software, you have no right under these Terms and Conditions to use our 85 | trademarks, trade names, service marks or product names. 86 | 87 | ## Grant of Future License 88 | 89 | We hereby irrevocably grant you an additional license to use the Software under 90 | the Apache License, Version 2.0 that is effective on the second anniversary of 91 | the date we make the Software available. On or after that date, you may use the 92 | Software under the Apache License, Version 2.0, in which case the following 93 | will apply: 94 | 95 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 96 | this file except in compliance with the License. 97 | 98 | You may obtain a copy of the License at 99 | 100 | http://www.apache.org/licenses/LICENSE-2.0 101 | 102 | Unless required by applicable law or agreed to in writing, software distributed 103 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 104 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 105 | specific language governing permissions and limitations under the License. 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > Marco is freshly extracted from PureBlazor. We're working through demos, documentations, and picking up the progress. You may find certain actions don't yet work and the code lacks organization. 3 | 4 | # Marco 5 | 6 | Marco was born as part of [PureBlazor](https://pureblazor.com). Initially, Marco was a simple block-based page builder for Blazor. Then, we added the capability to create and manage forms. 7 | 8 | PureBlazor is being sunset. Marco is now a standalone project, focused on providing a powerful and flexible block and form builder for Blazor applications. 9 | 10 | Depending on community interest, Marco will continue to evolve, adding new features and capabilities. We welcome contributions and feedback from the community. 11 | 12 | ## Roadmap 13 | Marco's roadmap includes the following features and improvements: 14 | 15 | ### General improvements 16 | - [ ] Remove dependency on PureBlazor 17 | - [ ] Refine existing block and form architecture 18 | - [ ] Improve documentation and examples 19 | - [ ] Unified programming model for Server and WebAssembly 20 | 21 | ### Forms 22 | - [ ] Persistent form data storage 23 | - [ ] Support for multiple storage providers (e.g., SQL, NoSQL, in-memory) 24 | - [ ] Support for custom storage providers 25 | - [ ] Form validation support 26 | - [ ] Client-side validation 27 | - [ ] Server-side validation 28 | - [ ] Minimum and maximum length validation 29 | - [ ] Required field validation 30 | - [ ] Regular expression validation 31 | - [ ] Form block types 32 | - [ ] Text input 33 | - [ ] Text area 34 | - [ ] Select input 35 | - [ ] Checkbox input 36 | - [ ] Radio input 37 | - [ ] Date input 38 | - [ ] Time input 39 | - [ ] Form layout support 40 | - [ ] Vertical layout 41 | - [ ] Form submission support 42 | - [ ] Submit form data to server with static page / enhanced navigation 43 | - [ ] Form actions 44 | - [ ] Submit button 45 | - [ ] Reset button 46 | - [ ] Submit latest revision of form 47 | - [ ] Form sections 48 | - [ ] Section block type 49 | - [ ] Section layout support 50 | - [ ] Reusable section blocks 51 | - [ ] Revision history support 52 | - [ ] New revision on each form change 53 | - [ ] View previous revisions 54 | - [ ] Restore previous revisions 55 | 56 | # License 57 | The [Functional Source License (FSL)](https://fsl.software/) is a Fair Source license that converts to Apache 2.0 or MIT after two years. In other words, you may use Marco in your software so long as it does not compete with our paid products. 58 | 59 | # Funding 60 | Marco is available under the [Functional Source License (FSL)](https://fsl.software/). If you find Marco useful, please consider supporting its development by purchasing a license or making a donation. 61 | 62 | Marco may become a part of our SaaS offering in the future, which will provide additional features and capabilities. By supporting Marco, you help ensure its continued development and improvement. You may embed Marco in your SaaS application, provided you comply with the terms of the FSL license. 63 | 64 | ## What can I build with Marco? 65 | 66 | Here's a demo of the page builder that was built for PureBlazor. 67 | 68 | https://github.com/user-attachments/assets/b05e86e4-60e2-4ecc-a6a2-8aa7fa6641fd 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/BlockAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record BlockAction(string BlockId) 4 | { 5 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/BlockActions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public enum BlockActions 4 | { 5 | Add, 6 | Modify, 7 | Delete 8 | } 9 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/ChangeSectionAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record ChangeSectionAction(string BlockId, string SectionId) : BlockAction(BlockId); 4 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/DebugBlockAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record DebugBlockAction(string BlockId) : BlockAction(BlockId); -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/DeleteBlockAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record DeleteBlockAction(string BlockId) : BlockAction(BlockId); 4 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/EditBlockAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record EditBlockAction(string BlockId) : BlockAction(BlockId) 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/InsertBlockAction.cs: -------------------------------------------------------------------------------- 1 | 2 | using Marco.Forms.UI.Client.Models; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Actions; 5 | 6 | public record InsertBlockAction(string BlockId) : BlockAction(BlockId) 7 | { 8 | /// 9 | /// Whether to insert the block before or after the current block. 10 | /// 11 | /// By default, the block is inserted after the current block. 12 | /// 13 | public bool InsertBefore { get; set; } 14 | 15 | /// 16 | /// The block definition to insert. 17 | /// 18 | public required BlockDefinition BlockDefinition { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/MoveAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record MoveAction(string BlockId) : BlockAction(BlockId); -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/MoveDownAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record MoveDownAction(string BlockId) : BlockAction(BlockId); -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Actions/MoveUpAction.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Actions; 2 | 3 | public record MoveUpAction(string BlockId) : BlockAction(BlockId); 4 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/BlockActionMessage.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Actions; 2 | 3 | namespace Marco.Forms.UI.Client.Components; 4 | 5 | public record BlockActionMessage 6 | { 7 | public BlockActionMessage(BlockActions action, PageBlock block) 8 | { 9 | Action = action; 10 | Block = block; 11 | } 12 | 13 | public BlockActions Action { get; } 14 | public PageBlock Block { get; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/BlockController.razor.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Models; 2 | using Microsoft.JSInterop; 3 | using Pure.Blazor.Components; 4 | 5 | namespace Marco.Forms.UI.Client.Components; 6 | 7 | public partial class BlockController( 8 | ILogger logger, 9 | DialogService dialogService, 10 | IJSRuntime jsRuntime) 11 | { 12 | private async Task OnEditorAction(EditorAction editorAction) 13 | { 14 | if (editorAction.Action == EditorActionType.Enter) 15 | { 16 | // Create a new block 17 | var block = CreateBlock(new BlockDefinition{ Id = BlockTypes.ContentEditable, Description = "", Icon = "", Name = ""}); 18 | 19 | var parentBlockId = CanvasState.SelectedBlock?.ParentBlockIds.FirstOrDefault(); 20 | if (parentBlockId is not null) 21 | { 22 | CanvasState.AddBlockAtRelativePosition(block, parentBlockId, CanvasState.SelectedBlock); 23 | await CanvasStateChanged.InvokeAsync(CanvasState); 24 | //CanvasState.SelectedBlock = block; 25 | } 26 | } 27 | } 28 | 29 | private PageBlock CreateBlock(BlockDefinition definition) 30 | { 31 | var block = new PageBlock 32 | { 33 | BlockId = Uuid.New(), 34 | PageId = Block.PageId, 35 | BlockType = definition.Id, 36 | Tag = definition.Id is BlockTypes.ContentEditable ? "p" : "div", 37 | Formatting = new() 38 | { 39 | Margin = new(1), 40 | Padding = new(1), 41 | FontFamily = "font-sans", 42 | FontSizeV2 = 1, 43 | TextColor = "rgba(0,0,0,0.8)", 44 | FontSizeUnit = CssUnit.Rem, 45 | Border = new Border { Color = "red", Width = 1, Style = "solid" } 46 | }, 47 | // only enable layout for section and root blocks 48 | Layout = definition.Id is BlockTypes.Section or BlockTypes.Root 49 | ? new BlockLayout() { GridLayoutOptions = new GridLayoutOptions { TemplateColumns = 1, } } 50 | : null 51 | }; 52 | block.BlockData = BlockDefaults.CreateBlockData(block); 53 | 54 | return block; 55 | } 56 | } 57 | 58 | public enum EditorActionType 59 | { 60 | Enter, 61 | Exit, 62 | Save, 63 | Cancel 64 | } 65 | 66 | public record EditorAction(EditorActionType Action) 67 | { 68 | public object? Data { get; set; } 69 | } 70 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/BlockLayout.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record BlockLayout 4 | { 5 | public int Version { get; set; } 6 | public BlockLayoutType LayoutType { get; set; } 7 | public GridLayoutOptions? GridLayoutOptions { get; set; } 8 | public FixedLayoutOptions? FixedLayoutOptions { get; set; } 9 | public Spacing Padding { get; set; } = new(); 10 | public Spacing Margin { get; set; } = new(); 11 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/BlockLayoutExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public static class BlockLayoutExtensions 4 | { 5 | public static string ToCssClass(this BlockLayout layout) 6 | { 7 | return $"{layout.GetCssClass()}"; //{layout.GridLayoutOptions.GetGridCssClass() 8 | } 9 | private static string GetCssClass(this BlockLayout layout) 10 | { 11 | return layout.LayoutType switch 12 | { 13 | BlockLayoutType.Grid => "grid", 14 | BlockLayoutType.Fixed => "fixed", 15 | _ => "grid" 16 | }; 17 | } 18 | 19 | /// 20 | /// Returns the inline CSS style for the block layout 21 | /// 22 | /// 23 | /// 24 | public static string GetGridCssStyle(this GridLayoutOptions? options) 25 | { 26 | if (options == null) 27 | { 28 | return "grid-template-columns: repeat(12, 1fr);"; 29 | } 30 | 31 | return options.TemplateColumns switch 32 | { 33 | 1 => "grid-template-columns: repeat(1, 1fr);", 34 | 2 => "grid-template-columns: repeat(2, 1fr);", 35 | 3 => "grid-template-columns: repeat(3, 1fr);", 36 | 4 => "grid-template-columns: repeat(4, 1fr);", 37 | 5 => "grid-template-columns: repeat(5, 1fr);", 38 | 6 => "grid-template-columns: repeat(6, 1fr);", 39 | 7 => "grid-template-columns: repeat(7, 1fr);", 40 | 8 => "grid-template-columns: repeat(8, 1fr);", 41 | 9 => "grid-template-columns: repeat(9, 1fr);", 42 | 10 => "grid-template-columns: repeat(10, 1fr);", 43 | 11 => "grid-template-columns: repeat(11, 1fr);", 44 | 12 => "grid-template-columns: repeat(12, 1fr);", 45 | _ => "grid-template-columns: repeat(1, 1fr);" 46 | }; 47 | } 48 | 49 | private static string GetGridCssClass(this GridLayoutOptions? options) 50 | { 51 | if (options == null) 52 | { 53 | return "grid-cols-12"; 54 | } 55 | 56 | return options.TemplateColumns switch 57 | { 58 | 1 => "grid-cols-1", 59 | 2 => "grid-cols-2", 60 | 3 => "grid-cols-3", 61 | 4 => "grid-cols-4", 62 | 5 => "grid-cols-5", 63 | 6 => "grid-cols-6", 64 | 7 => "grid-cols-7", 65 | 8 => "grid-cols-8", 66 | 9 => "grid-cols-9", 67 | 10 => "grid-cols-10", 68 | 11 => "grid-cols-11", 69 | 12 => "grid-cols-12", 70 | _ => "grid-cols-12" 71 | }; 72 | } 73 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/BlockLayoutType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public enum BlockLayoutType 4 | { 5 | Auto, 6 | Fixed, 7 | Flex, 8 | Grid 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/BlockComponentBase.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Models; 2 | using Microsoft.AspNetCore.Components; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Blocks; 5 | 6 | public class BlockComponentBase : ComponentBase where T : BlockData 7 | { 8 | /// 9 | /// Blocks can request the next block to be created by invoking this callback. 10 | /// 11 | /// The block editor will handle the creation of the next block. If the block definition is null, 12 | /// the block editor should choose the next block type, usually a paragraph block. 13 | /// 14 | [Parameter] 15 | public EventCallback OnNextBlockRequested { get; set; } 16 | 17 | [Parameter] public EventCallback BlockSelected { get; set; } 18 | [Parameter] public EventCallback BlockChanged { get; set; } 19 | [Parameter, EditorRequired] public required PageBlock Block { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/BlockDefinitionGroupTitle.razor: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 | 4 | 5 | 6 |
    7 |
    @Title
    8 |
  • 9 | 10 | @code { 11 | [Parameter] 12 | public string? Title { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/BlockDefinitionList.razor: -------------------------------------------------------------------------------- 1 | @using System.Collections.Frozen 2 | @using Marco.Forms.UI.Client.Models 3 | @using Pure.Blazor.Components 4 |
      5 |
    • 6 |
      7 | 8 | 9 | 10 |
      11 |
      @GroupName
      12 |
    • 13 | @foreach (var blockDef in Blocks) 14 | { 15 |
    • 16 |
      17 | @((MarkupString)blockDef.Icon) 18 |
      19 |
      20 |
      @blockDef.Name
      21 |
      @blockDef.Description
      22 |
      23 |
    • 24 | } 25 | @if (Blocks.Count == 0) 26 | { 27 |
    • No blocks found
    • 28 | } 29 |
    30 | 31 | @code { 32 | private string? FocusedBlockId { get; set; } 33 | 34 | [Parameter] public string GroupName { get; set; } = "Blocks"; 35 | 36 | [Parameter] public FrozenSet Blocks { get; set; } = []; 37 | [Parameter] public EventCallback OnBlockSelected { get; set; } 38 | 39 | private async Task SelectBlockAsync(BlockDefinition blockDefinition) 40 | { 41 | await OnBlockSelected.InvokeAsync(blockDefinition); 42 | } 43 | 44 | private async Task OnArrowKey(KeyboardDirection direction) 45 | { 46 | var task = direction switch 47 | { 48 | KeyboardDirection.Down => MoveDownAsync(), 49 | KeyboardDirection.Up => MoveUpAsync(), 50 | _ => Task.CompletedTask 51 | }; 52 | 53 | await task; 54 | } 55 | 56 | private Task MoveDownAsync() 57 | { 58 | if (FocusedBlockId is null) 59 | { 60 | // Focus on the first block 61 | FocusedBlockId = Blocks.FirstOrDefault()?.Id; 62 | return Task.CompletedTask; 63 | } 64 | 65 | // Focus on the next block, if found. otherwise, stay on the current block. 66 | FocusedBlockId = Blocks.SkipWhile(b => b.Id != FocusedBlockId).Skip(1).FirstOrDefault()?.Id ?? FocusedBlockId; 67 | 68 | return Task.CompletedTask; 69 | } 70 | 71 | private Task MoveUpAsync() 72 | { 73 | if (FocusedBlockId is null) 74 | { 75 | // Focus on the first block 76 | FocusedBlockId = Blocks.FirstOrDefault()?.Id; 77 | return Task.CompletedTask; 78 | } 79 | 80 | // Focus on the previous block, if found. otherwise, stay on the current block. 81 | FocusedBlockId = Blocks.TakeWhile(b => b.Id != FocusedBlockId).LastOrDefault()?.Id ?? FocusedBlockId; 82 | 83 | return Task.CompletedTask; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/BlockEditContainer.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Actions 2 | @using Marco.Forms.UI.Client.Models 3 | @using Pure.Blazor.Components.Icons 4 | @using Marco.Forms.UI.Client.Components.Controls 5 | @using Marco.Forms.UI.Client.Components.Properties 6 | 7 | 10 | 11 | 12 | @code { 13 | private RenderFragment IconEllipsis => builder => 14 | { 15 | builder.OpenComponent(0, typeof(PureIcon)); 16 | builder.AddAttribute(1, nameof(PureIcon.Icon), PureIcons.IconEllipsis); 17 | builder.CloseComponent(); 18 | }; 19 | 20 | private RenderFragment GrabHandle => builder => 21 | { 22 | builder.AddMarkupContent(0, ""); 23 | }; 24 | [Inject] 25 | public required BlockDefinitionService BlockDefinitionService { get; set; } 26 | private FloatingContainer contextMenuSelector = null!; 27 | private FloatingContainer topBlockSelector = null!; 28 | private FloatingContainer bottomBlockSelector = null!; 29 | public BlockDefinition[] Blocks { get; set; } = []; 30 | 31 | protected override void OnInitialized() 32 | { 33 | Blocks = BlockDefinitionService.GetBlocks(); 34 | } 35 | 36 | public async Task BlockActionSelected(BlockAction action) 37 | { 38 | contextMenuSelector.CloseMenu(); 39 | await ActionSelected.InvokeAsync(action); 40 | } 41 | 42 | public async Task BlockSelected(BlockDefinition block) 43 | { 44 | bottomBlockSelector.CloseMenu(); 45 | topBlockSelector.CloseMenu(); 46 | 47 | // TODO: send 1) which block the action is for, 2) which block definition was selected, and 3) insert before/after 48 | var action = new InsertBlockAction(Block.BlockId) 49 | { 50 | BlockDefinition = block 51 | }; 52 | 53 | await ActionSelected.InvokeAsync(action); 54 | } 55 | 56 | [Parameter] public required PageBlock Block { get; set; } 57 | 58 | [Parameter] public required EventCallback ActionSelected { get; set; } 59 | } 60 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/ContainerBlockView.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Blocks; 5 | 6 | public class ContainerBlockView : ComponentBase 7 | { 8 | [Parameter] public RenderFragment? ChildContent { get; set; } 9 | [Parameter] public required PageBlock Block { get; set; } 10 | 11 | protected override void BuildRenderTree(RenderTreeBuilder builder) 12 | { 13 | builder.OpenElement(0, "div"); 14 | builder.AddAttribute(1, "class", $"relative"); 15 | 16 | builder.AddContent(3, ChildContent); 17 | builder.CloseElement(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/IBlazorBlock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Blocks; 4 | 5 | public interface IBlazorBlock 6 | { 7 | /// 8 | /// Optional. Builds the editor view for the block. This is the visual representation of the block when it is 9 | /// being edited in Marco. 10 | /// 11 | public RenderFragment Editor() => 12 | builder => { }; 13 | 14 | /// 15 | /// Optional. Editor activation occurs when the block is selected or focused. This is the point at which the 16 | /// block should prepare for user interaction during editing. 17 | /// For example, if your editor view contains an input element, you could focus the input element. 18 | /// 19 | public Task OnEditorActivatedAsync() => Task.CompletedTask; 20 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/LinkBlock.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Models; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Blocks; 6 | 7 | public class LinkBlock : ComponentBase, IBlazorBlock 8 | { 9 | [Parameter] public required PageBlock Block { get; set; } 10 | 11 | protected override void BuildRenderTree(RenderTreeBuilder builder) 12 | { 13 | if (Block.BlockData is LinkBlockData data) 14 | { 15 | builder.OpenElement(0, "a"); 16 | builder.AddAttribute(1, "href", data.Url); 17 | builder.AddAttribute(2, "class", $"hover:underline {Block.Formatting?.ToCssClass()}"); 18 | builder.AddAttribute(3, "style", Block.Formatting?.ToCssStyle()); 19 | builder.AddContent(4, data.Text); 20 | builder.CloseElement(); 21 | } 22 | else 23 | { 24 | builder.OpenElement(5, "div"); 25 | builder.AddContent(6, "Add a link to the block"); 26 | builder.CloseElement(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/ListBlock.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Models; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Blocks; 6 | 7 | public class ListBlock : ComponentBase, IBlazorBlock 8 | { 9 | [Parameter] public required PageBlock Block { get; set; } 10 | 11 | protected override void BuildRenderTree(RenderTreeBuilder builder) 12 | { 13 | if (Block.BlockData is ListBlockData data) 14 | { 15 | builder.OpenElement(0, data.ListType == ListType.Ordered ? "ol" : "ul"); 16 | builder.AddAttribute(1, "class", 17 | $"{(data.ListType == ListType.Ordered ? "list-disc" : "list-decimal")} {Block.Formatting?.ToCssClass()}"); 18 | builder.AddAttribute(2, "style", Block.Formatting?.ToCssStyle()); 19 | foreach (var item in data.Items) 20 | { 21 | builder.OpenElement(3, "li"); 22 | builder.AddContent(4, item.Text); 23 | builder.CloseElement(); 24 | } 25 | 26 | builder.CloseElement(); 27 | } 28 | else 29 | { 30 | builder.OpenElement(5, "div"); 31 | builder.AddContent(6, "Add items to the list"); 32 | builder.CloseElement(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/SectionBlock.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Models 2 | @using Marco.Forms.UI.Client.Components.Shared 3 | @inherits BlockComponentBase 4 | 5 |
    6 | @foreach (var block in PageBlocks) 7 | { 8 | 9 | 10 | 15 | 16 | 17 |

    An error occurred while loading the block

    18 |
    19 |
    20 | } 21 | @if (!PageBlocks.Any() && !HasError) 22 | { 23 | 24 |

    Empty section

    25 | } 26 | else if (HasError) 27 | { 28 |

    An error occurred while loading the section

    29 | } 30 |
    31 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/SectionBlock.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Blocks; 4 | 5 | public partial class SectionBlock( 6 | ILogger logger) 7 | { 8 | [Parameter, EditorRequired] public required string SiteId { get; set; } 9 | [Parameter, EditorRequired] public required MarcoCanvasState CanvasState { get; set; } 10 | [Parameter, EditorRequired] public required EventCallback CanvasStateChanged { get; set; } 11 | 12 | public IEnumerable PageBlocks { get; set; } = []; 13 | 14 | public bool HasError { get; set; } 15 | 16 | protected override Task OnInitializedAsync() 17 | { 18 | logger.LogInformation("Loading blocks for section {sectionId}", Block.BlockId); 19 | HasError = false; 20 | var childBlockIds = Block.ChildBlockIds ?? []; 21 | PageBlocks = CanvasState.Blocks.Where(p => childBlockIds.Contains(p.BlockId)).ToList().OrderBy(p => p.BlockPosition); 22 | return Task.CompletedTask; 23 | } 24 | 25 | protected override void OnParametersSet() 26 | { 27 | logger.LogInformation("Setting parameters for section {sectionId}", Block.BlockId); 28 | var childBlockIds = Block.ChildBlockIds ?? []; 29 | 30 | PageBlocks = CanvasState.Blocks.Where(p => childBlockIds.Contains(p.BlockId)).OrderBy(p => p.BlockPosition).ToList().OrderBy(p => p.BlockPosition); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/SectionViewBlock.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Models 2 | @using Marco.Forms.UI.Client.Components.Shared 3 | @inherits BlockComponentBase 4 | 5 |
    6 | @foreach (var block in PageBlocks.OrderBy(p => p.BlockPosition)) 7 | { 8 | 9 | 10 | 11 | 12 | 13 |

    An error occurred while loading the block

    14 |
    15 |
    16 | } 17 | @if (!PageBlocks.Any() && !HasError) 18 | { 19 | 20 |

    Empty section

    21 | } 22 | else if (HasError) 23 | { 24 |

    An error occurred while loading the section

    25 | } 26 |
    27 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/SectionViewBlock.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Blocks; 4 | 5 | public partial class SectionViewBlock( 6 | ILogger logger) 7 | { 8 | [Parameter] public IEnumerable PageBlocks { get; set; } = []; 9 | 10 | [Parameter, EditorRequired] public required string SiteId { get; set; } 11 | public bool HasError { get; set; } 12 | 13 | protected override Task OnInitializedAsync() 14 | { 15 | logger.LogInformation("Loading blocks for section {sectionId}", Block.BlockId); 16 | HasError = false; 17 | return Task.CompletedTask; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Blocks/StatePickerBlock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Blocks; 5 | 6 | public class StatePickerBlock : ComponentBase, IBlazorBlock 7 | { 8 | public RenderFragment Editor() => 9 | builder => 10 | { 11 | builder.OpenElement(0, "select"); 12 | builder.OpenElement(1, "option"); 13 | builder.AddAttribute(2, "value", "Alaska"); 14 | builder.AddContent(3, "Alaska"); 15 | builder.CloseElement(); 16 | builder.CloseElement(); 17 | }; 18 | 19 | protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, "Alaska"); 20 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Controls/ColorPicker.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Selected Color: @Color

    4 | 5 | @code { 6 | [Parameter] 7 | public string Color { get; set; } = "#000000"; 8 | 9 | [Parameter] 10 | public EventCallback ColorChanged { get; set; } 11 | 12 | private async Task OnColorChanged(ChangeEventArgs e) 13 | { 14 | Color = e.Value?.ToString() ?? "#000000"; 15 | await ColorChanged.InvokeAsync(Color); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Controls/DockableContent.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Properties 2 |
    5 | @ChildContent 6 | @(IsDocked ? "Undock" : "Dock") 7 |
    8 | @code { 9 | private string ContainerClasses => IsDocked 10 | ? "w-80 shrink-0 h-[calc(100vh-64px)] bg-gray-50 overflow-y-auto border-t-1 border-gray-200" 11 | : $"bg-gray-50 shadow-md min-h-80 max-h-full min-w-48 max-w-96 overflow-y-auto rounded-xl outline-1 outline-gray-200 {ScrollbarStyles} fixed"; 12 | 13 | private string ContainerStyles => IsDocked 14 | ? "" 15 | : $"{GetPositionBasedOnAnchor()} z-index: 100;"; 16 | public bool IsDocked { get; set; } = true; 17 | private const string ScrollbarStyles = "[&::-webkit-scrollbar]:[width:10px] [&::-webkit-scrollbar]:bg-gray-200 [&::-webkit-scrollbar]:border-r-1 [&::-webkit-scrollbar]:border-gray-300 [&::-webkit-scrollbar]:rounded-tr-xl [&::-webkit-scrollbar]:rounded-br-xl [&::-webkit-scrollbar-thumb]:rounded-tr-xl [&::-webkit-scrollbar-thumb]:rounded-br-xl [&::-webkit-scrollbar-thumb]:bg-gray-300"; 18 | 19 | [Parameter] public EventCallback OnClickCancel { get; set; } 20 | 21 | [Parameter] public RenderFragment? ChildContent { get; set; } 22 | 23 | [Parameter] public (double X, double Y) Position { get; set; } 24 | 25 | [Parameter] public PositionAnchor PositionAnchor { get; set; } 26 | 27 | private BoxPosition ContentAnchor { get; set; } = BoxPosition.TopLeft; 28 | public ElementReference ContentContainer { get; private set; } 29 | 30 | private string GetPositionBasedOnAnchor() 31 | { 32 | return PositionAnchor switch 33 | { 34 | PositionAnchor.End => $"top: {Position.Y}px; right: {Position.X}px; {ContentAnchor.GetTransformCSS()};", 35 | _ => $"top: {Position.Y}px; left: {Position.X}px; {ContentAnchor.GetTransformCSS()};", 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Controls/FloatingContainer.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Properties 2 | @using Pure.Blazor.Components.Icons 3 | 21 | @if (WindowPositioner is not null) 22 | { 23 | 24 | @ChildContent 25 | 26 | } 27 | 28 | @code { 29 | private const string ScrollbarStyles = "[&::-webkit-scrollbar]:[width:10px] [&::-webkit-scrollbar]:bg-gray-200 [&::-webkit-scrollbar]:border-r-1 [&::-webkit-scrollbar]:border-gray-300 [&::-webkit-scrollbar]:rounded-tr-lg [&::-webkit-scrollbar]:rounded-br-lg [&::-webkit-scrollbar-thumb]:rounded-tr-lg [&::-webkit-scrollbar-thumb]:rounded-br-lg [&::-webkit-scrollbar-thumb]:bg-gray-300"; 30 | private bool Visible => WindowPositioner is not null; 31 | private (double X, double Y) position => WindowPositioner?.GetPosition() ?? (0, 0); 32 | 33 | [Parameter] 34 | public string? ElementId { get; set; } 35 | 36 | [Parameter] 37 | public RenderFragment? ChildContent { get; set; } 38 | 39 | [Parameter] 40 | public BoxPosition BoxPosition { get; set; } = BoxPosition.TopLeft; 41 | 42 | [Parameter, EditorRequired] public required RenderFragment Icon { get; set; } 43 | private IPositioner? WindowPositioner { get; set; } = null; 44 | 45 | public void ToggleMenu(MouseEventArgs e) 46 | { 47 | WindowPositioner = WindowPositioner is null ? ScreenPositioner.New(e) : null; 48 | } 49 | 50 | public void CloseMenu() 51 | { 52 | WindowPositioner = null; 53 | } 54 | 55 | private Task CancelAsync() 56 | { 57 | WindowPositioner = null; 58 | return Task.CompletedTask; 59 | } 60 | 61 | private string GetRounding() 62 | { 63 | if (WindowPositioner is not null) 64 | { 65 | return "rounded-full"; 66 | } 67 | 68 | return BoxPosition switch 69 | { 70 | BoxPosition.MiddleLeft => "rounded-l", 71 | BoxPosition.BottomCenter => "rounded-b", 72 | _ => "rounded-t" 73 | }; 74 | } 75 | private string GetBoxStyles() 76 | { 77 | return BoxPosition switch 78 | { 79 | BoxPosition.TopCenter => "left: 50%; top:0; transform: translate(-50%, -100%);", 80 | BoxPosition.MiddleLeft => "left: 0; top: 50%; transform: translate(-100%, -50%);", 81 | BoxPosition.BottomCenter => "left: 50%; transform: translate(-50%, 0%);", 82 | _ => "top: 0; left: 0;" 83 | }; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Controls/FloatingContainerContent.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Properties 2 |
    3 |
    4 | @ChildContent 5 |
    6 | 7 | 8 | @code { 9 | private const string ScrollbarStyles = "[&::-webkit-scrollbar]:[width:10px] [&::-webkit-scrollbar]:bg-gray-200 [&::-webkit-scrollbar]:border-r-1 [&::-webkit-scrollbar]:border-gray-300 [&::-webkit-scrollbar]:rounded-tr-lg [&::-webkit-scrollbar]:rounded-br-lg [&::-webkit-scrollbar-thumb]:rounded-tr-lg [&::-webkit-scrollbar-thumb]:rounded-br-lg [&::-webkit-scrollbar-thumb]:bg-gray-300"; 10 | private bool Visible => WindowPositioner is not null; 11 | private (double X, double Y) position => WindowPositioner?.GetPosition() ?? (0, 0); 12 | 13 | [Parameter] 14 | public EventCallback OnClickCancel{ get; set; } 15 | 16 | [Parameter] 17 | public RenderFragment? ChildContent { get; set; } 18 | 19 | 20 | [Parameter] 21 | public IPositioner? WindowPositioner { get; set; } = null; 22 | 23 | [Inject] 24 | public required IJSRuntime JSRuntime { get; init; } 25 | 26 | private BoxPosition ContentAnchor { get; set; } = BoxPosition.TopLeft; 27 | public ElementReference ContentContainer { get; private set; } 28 | 29 | protected override async Task OnAfterRenderAsync(bool firstRender) 30 | { 31 | if (firstRender) 32 | { 33 | var module = await JSRuntime.InvokeAsync("import", "../js/offscreenUtil.js"); 34 | var isSelectorBottomOffscreen = await module.InvokeAsync("isBottomOffscreen", ContentContainer); 35 | if (isSelectorBottomOffscreen) 36 | { 37 | ContentAnchor = BoxPosition.BottomLeft; 38 | StateHasChanged(); 39 | } 40 | await module.DisposeAsync(); // We only call it once, since blazor doesn't have a native way to check for resizing 41 | } 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/CssUnit.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public enum CssUnit 4 | { 5 | Unset, 6 | Px, 7 | Em, 8 | Rem 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/DomRect.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record DomRect 4 | { 5 | public double Bottom { get; set; } 6 | public double Height { get; set; } 7 | public double Left { get; set; } 8 | public double Right { get; set; } 9 | public double Top { get; set; } 10 | public double Width { get; set; } 11 | public double X { get; set; } 12 | public double Y { get; set; } 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/ExampleWizard.razor: -------------------------------------------------------------------------------- 1 | @page "/example-wizard" 2 | @using Marco.Forms.UI.Client.Components.Forms 3 | @rendermode InteractiveServer 4 | 5 | @*

    Example Wizard Usage

    *@ 6 |
    7 | 8 |
    9 | @* *@ 10 | @*
    *@ 11 | @* *@ 12 | @* *@ 13 | @* *@ 14 | @*
    *@ 15 | @*

    Personal Info

    *@ 16 | @*

    Ask for user personal info here...

    *@ 17 | @*
    *@ 18 | @*
    *@ 19 | @* *@ 20 | @* *@ 21 | @*
    *@ 22 | @*

    Additional Details

    *@ 23 | @*

    Some other information the user must fill out...

    *@ 24 | @*
    *@ 25 | @*
    *@ 26 | @* *@ 27 | @* *@ 28 | @*
    *@ 29 | @*

    Review & Submit

    *@ 30 | @*

    Review your data here...

    *@ 31 | @*
    *@ 32 | @*
    *@ 33 | @*
    *@ 34 | 35 | @code { 36 | 37 | private void HandleWizardFinished() 38 | { 39 | // The wizard completed. Save data, redirect, etc. 40 | Console.WriteLine("Wizard finished!"); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/FixedLayoutOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record FixedLayoutOptions 4 | { 5 | /// 6 | /// The width of the element. 7 | /// 8 | public double Width { get; set; } 9 | 10 | /// 11 | /// The height of the element. 12 | /// 13 | public double Height { get; set; } 14 | 15 | /// 16 | /// The minimum width of the element. 17 | /// 18 | public double? MinWidth { get; set; } 19 | 20 | /// 21 | /// The minimum height of the element. 22 | /// 23 | public double? MinHeight { get; set; } 24 | 25 | /// 26 | /// The maximum width of the element. 27 | /// 28 | public double? MaxWidth { get; set; } 29 | 30 | /// 31 | /// The maximum height of the element. 32 | /// 33 | public double? MaxHeight { get; set; } 34 | 35 | /// 36 | /// The x position of the element, relative to its parent. 37 | /// 38 | public double? X { get; set; } 39 | 40 | /// 41 | /// The y position of the element, relative to its parent. 42 | /// 43 | public double? Y { get; set; } 44 | 45 | /// 46 | /// The z-index of the element. 47 | /// 48 | public int? ZIndex { get; set; } 49 | 50 | /// 51 | /// The CSS unit for the element's size properties. 52 | /// 53 | public CssUnit WidthUnit { get; set; } 54 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/CheckboxBlock.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Blocks; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Forms; 6 | 7 | public class CheckboxBlock : ComponentBase, IBlazorBlock 8 | { 9 | [Parameter] public required FieldSchema Field { get; set; } 10 | 11 | protected override void BuildRenderTree(RenderTreeBuilder builder) 12 | { 13 | var fragment = Render(); 14 | fragment(builder); 15 | } 16 | 17 | private RenderFragment Render() 18 | { 19 | return builder => 20 | { 21 | builder.OpenElement(0, "div"); 22 | builder.AddAttribute(1, "class", "form-check flex items-center"); 23 | 24 | builder.OpenElement(10, "label"); 25 | builder.AddAttribute(11, "class", "form-check-label text-sm font-medium text-gray-700 px-2"); 26 | builder.AddContent(13, Field.Name); 27 | builder.CloseElement(); 28 | builder.OpenElement(100, "div"); 29 | builder.AddAttribute(101, "class", "grid auto-cols-max grid-flow-col gap-2"); 30 | foreach (var f in Field.Options) 31 | { 32 | builder.OpenElement(14, "div"); 33 | builder.AddAttribute(15, "class", "text-xs text-gray-500 flex gap-2 p-2 text-md"); 34 | // builder.AddContent(16, f.Label); 35 | builder.OpenRegion(17); 36 | builder.OpenElement(2, "input"); 37 | builder.AddAttribute(3, "class", "form-check-input"); 38 | builder.AddAttribute(4, "type", "checkbox"); 39 | builder.AddAttribute(5, "id", $"Field.FormFieldId_{f.Label}"); 40 | builder.AddAttribute(6, "name", $"Field.FormFieldId_{f.Label}"); 41 | builder.AddAttribute(7, "value", "true"); 42 | builder.AddAttribute(8, "checked", f.Value.BooleanValue == true); 43 | builder.AddAttribute(9, "onchange", 44 | EventCallback.Factory.Create(this, e => OnChange(f, e))); 45 | builder.CloseElement(); 46 | 47 | builder.OpenElement(10, "label"); 48 | builder.AddAttribute(11, "class", "form-check-label text-sm font-medium text-gray-700 px-2"); 49 | builder.AddAttribute(12, "for", f.Label); 50 | builder.AddContent(13, f.Label); 51 | builder.CloseElement(); 52 | builder.CloseRegion(); 53 | 54 | builder.CloseElement(); 55 | } 56 | builder.CloseElement(); 57 | 58 | builder.CloseElement(); 59 | }; 60 | } 61 | 62 | private void OnChange(FormFieldOption o, ChangeEventArgs e) 63 | { 64 | if (e.Value is bool value) 65 | { 66 | o.Value.BooleanValue = value; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/CheckboxField.razor: -------------------------------------------------------------------------------- 1 |
    2 | @* *@ 3 |
    4 | @foreach (var option in Field.Options) 5 | { 6 | var o = option; 7 |
    8 | 27 | @* *@ 32 | @* *@ 37 | 38 | 40 |
    41 | } 42 |
    43 |
    44 | 45 | @code { 46 | [Parameter] public required FieldSchema Field { get; set; } 47 | 48 | private void Toggle(FormFieldOption option) 49 | { 50 | var oldValue = option.Value.BooleanValue ?? false; 51 | var newValue = !oldValue; 52 | Console.WriteLine("Toggling option " + option.Label + " from " + oldValue + " to " + newValue); 53 | option.Value.BooleanValue = newValue; 54 | StateHasChanged(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FieldSchema.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public class FieldSchema 4 | { 5 | public string? FormFieldId { get; set; } 6 | public string? Label { get; set; } 7 | public string? Name { get; set; } 8 | public string? Description { get; set; } 9 | public FieldType Type { get; set; } 10 | public bool Required { get; set; } 11 | public FieldValue Value { get; set; } = new(); 12 | public List Options { get; set; } = []; 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FieldType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public enum FieldType 4 | { 5 | /// 6 | /// Used for single line text input, such as names, short answers, etc. 7 | /// 8 | Text = 0, 9 | 10 | /// 11 | /// Used for multi-line text input, such as paragraphs, descriptions, etc. 12 | /// 13 | TextArea = 1, 14 | 15 | /// 16 | /// Used for selecting a specific date, such as a birthday, appointment date, etc. 17 | /// 18 | Date = 2, 19 | 20 | /// 21 | /// Used for selecting a specific time, such as an appointment time, etc. 22 | /// 23 | Time = 3, 24 | 25 | /// 26 | /// Used for selecting a specific date and time, such as an appointment date and time, creation time, etc. 27 | /// 28 | DateTime = 4, 29 | 30 | /// 31 | /// Used for selecting a specific number, such as age, quantity, etc. 32 | /// 33 | Select = 5, 34 | 35 | /// 36 | /// Used when multiple, independent selections are possible or for a simple on/off choice. 37 | /// Commonly seen in forms for multi-select options (e.g., selecting multiple hobbies or filters), 38 | /// toggling preferences (e.g., enabling/disabling notifications), or confirming acknowledgments (e.g., 39 | /// agreeing to terms and conditions). Avoid using checkboxes when the number if you have a long list (e.g., 20+ items). 40 | /// 41 | Checkbox = 6, 42 | 43 | /// 44 | /// Used when a user needs to select one option from a list of options. Typically used when 45 | /// there are only a few options, making a select dropdown unnecessarily large. 46 | /// 47 | Radio = 7 48 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FieldValue.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | /// 4 | /// Union-like type for the different types of values a form field can have. 5 | /// 6 | public class FieldValue 7 | { 8 | public FieldValueType ValueType { get; set; } 9 | public string? TextValue { get; set; } 10 | public HashSet? MultiValue { get; set; } 11 | public DateTime? DateValue { get; set; } 12 | public decimal? NumberValue { get; set; } 13 | public bool? BooleanValue { get; set; } 14 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FieldValueType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public enum FieldValueType 4 | { 5 | Text = 0, 6 | Multi, 7 | Date, 8 | Number, 9 | Boolean, 10 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/Form.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record Form(string FormId, string Name) 4 | { 5 | public string FormId { get; set; } = FormId; 6 | public string Name { get; set; } = Name; 7 | 8 | /// 9 | /// Tracks the version of the form, incremented each time the form is updated. 10 | /// 11 | /// This is useful for tracking changes to the form over time, and can be used to determine if a form submission is still valid - i.e. if the form has changed since the submission was made. 12 | /// 13 | public int Version { get; set; } 14 | 15 | public FormStatus Status { get; set; } 16 | public DateTime? ScheduledDate { get; set; } 17 | public DateTime? StartedDate { get; set; } 18 | public DateTime? CompletedDate { get; set; } 19 | public string? Description { get; set; } 20 | public string? Notes { get; set; } 21 | 22 | /// 23 | /// The unique link token that can be used to access the form. 24 | /// 25 | /// This is useful for sharing the form with others. This does not provide any security. 26 | /// 27 | public string? LinkIdentifier { get; set; } 28 | 29 | public string? PublicLink { get; set; } 30 | 31 | public string Completion { get; set; } = ""; 32 | public string Updated { get; set; } = ""; 33 | 34 | public HashSet Sections { get; set; } = []; 35 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormBuilderContainer.razor: -------------------------------------------------------------------------------- 1 | @page "/form-builder" 2 | 3 | @inject IFormSuggestionService AIService 4 | 5 |
    6 | 10 | 11 | @* *@ 14 | 15 | @if (IsStepByStepMode) 16 | { 17 | 22 | } 23 |
    24 | 25 | @code { 26 | private FormSchema CurrentForm = new FormSchema(); 27 | private bool IsStepByStepMode = true; 28 | private int SelectedPosition = 0; 29 | 30 | private void ToggleStepByStepMode() 31 | { 32 | IsStepByStepMode = !IsStepByStepMode; 33 | } 34 | 35 | private void ShowStepByStepMode(int position) 36 | { 37 | // Called if user tries to insert field at a certain position 38 | IsStepByStepMode = true; 39 | SelectedPosition = position; 40 | // Possibly store the position so the wizard knows where to insert 41 | } 42 | 43 | private void OnFieldAccepted((FieldSchema[] newField, int position) args) 44 | { 45 | var (newField, position) = args; 46 | if (newField.Length == 0) return; 47 | var last = Math.Max(0, CurrentForm.Fields.Count); 48 | 49 | // todo: support surgical insertion 50 | CurrentForm.Fields.InsertRange(last, newField); 51 | // re-serialize to JSON if needed 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormCanvas.razor: -------------------------------------------------------------------------------- 1 | @page "/forms/{FormId}/canvas" 2 | @using Marco.Forms.UI.Client.Components.Shared 3 | @using Microsoft.AspNetCore.Components.Sections 4 | @using Pure.Blazor.Components.Display 5 | @inject FormStore Store 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    14 |
    15 |
    16 |
    17 | 18 | @if (form is null) 19 | { 20 |

    Loading form...

    21 | } 22 | else 23 | { 24 |

    @form.Name

    25 |

    Manage form sections and fields.

    26 | 27 | } 28 |
    29 |
    30 | 31 | @code { 32 | public FormDto? form { get; set; } 33 | [Parameter] public string? FormId { get; set; } 34 | 35 | protected override async Task OnInitializedAsync() 36 | { 37 | if (FormId is not null) 38 | { 39 | var forms = await Store.GetFormsAsync(); 40 | form = forms.FirstOrDefault(f => f.FormId == FormId) ?? new(); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormDto.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record FormDto 4 | { 5 | public string FormId { get; set; } = ""; 6 | public string Name { get; set; } = ""; 7 | 8 | /// 9 | /// Tracks the version of the form, incremented each time the form is updated. 10 | /// 11 | /// This is useful for tracking changes to the form over time, and can be used to determine if a form submission is still valid - i.e. if the form has changed since the submission was made. 12 | /// 13 | public int Version { get; set; } 14 | 15 | public FormStatus Status { get; set; } 16 | public DateTime? ScheduledDate { get; set; } 17 | public DateTime? StartedDate { get; set; } 18 | public DateTime? CompletedDate { get; set; } 19 | public string? Description { get; set; } 20 | public string? Notes { get; set; } 21 | 22 | /// 23 | /// The unique link token that can be used to access the form. 24 | /// 25 | /// This is useful for sharing the form with others. This does not provide any security. 26 | /// 27 | public string? LinkIdentifier { get; set; } 28 | 29 | public string? PublicLink { get; set; } 30 | 31 | public int Responses { get; set; } 32 | public string Completion { get; set; } = ""; 33 | public string Updated { get; set; } = ""; 34 | 35 | public HashSet Sections { get; set; } = []; 36 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormEditor.razor: -------------------------------------------------------------------------------- 1 |
    2 | @for (var i = 0; i < FormSchema.Fields.Count; i++) 3 | { 4 | var i1 = i; 5 | 6 |
    7 | 8 | 9 | 10 | @* *@ 19 |
    20 | } 21 | @if (FormSchema.Fields.Count == 0) 22 | { 23 |

    Blank canvas

    24 | } 25 |
    26 | 27 | @code { 28 | [Parameter, EditorRequired] public required FormSchema FormSchema { get; set; } 29 | [Parameter] public EventCallback OnInsertField { get; set; } 30 | [Parameter] public int SelectedPosition { get; set; } 31 | } 32 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormField.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record FormField(string FormFieldId, string Name) 4 | { 5 | public string Name { get; set; } = Name; 6 | public string? Description { get; set; } 7 | public FieldType Type { get; set; } 8 | public bool Required { get; set; } 9 | public FieldValue Value { get; set; } = new(); 10 | public List Options { get; set; } = []; 11 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormFieldEditor.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | @code { 4 | [Parameter] 5 | public FieldSchema? Field { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormFieldOption.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public class FormFieldOption 4 | { 5 | public required string Label { get; set; } 6 | public required FieldValue Value { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormFieldRenderer.razor: -------------------------------------------------------------------------------- 1 | @using Pure.Blazor.Components 2 | @using Marco.Forms.UI.Client.Components.Shared 3 | 4 | 5 |
    6 | @* @if (Field.Type != FieldType.Checkbox) *@ 7 | @* { *@ 8 | @* // Don't render a legend for checkboxes, as they have their own label *@ 9 | @Field.Name 11 | @* } *@ 12 | @switch (Field.Type) 13 | { 14 | case FieldType.Text: 15 | 18 | break; 19 | case FieldType.TextArea: 20 | 23 | break; 24 | 25 | @* case FieldType.Email: *@ 26 | @* *@ 32 | @* break; *@ 33 | 34 | case FieldType.Checkbox: 35 | 36 | break; 37 | 38 | case FieldType.Radio: 39 | 40 | break; 41 | 42 | case FieldType.Date: 43 | 49 | break; 50 | 51 | case FieldType.Time: 52 | 58 | break; 59 | 60 | case FieldType.DateTime: 61 | 67 | break; 68 | case FieldType.Select: 69 | 70 | break; 71 | } 72 | 73 |
    74 |
    75 | Error rendering the field 76 |
    77 | 78 | @code { 79 | [Parameter, EditorRequired] public required FieldSchema Field { get; set; } 80 | } 81 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormPreviewDialog.razor: -------------------------------------------------------------------------------- 1 | @implements Microsoft.FluentUI.AspNetCore.Components.IDialogContentComponent 2 | @Content.Name 3 | @if (submitted) 4 | { 5 |

    Thank you! We've recorded your response.

    6 | } 7 | else 8 | { 9 | 11 | 12 | 13 | 14 | @foreach (var formSection in Content.Sections) 15 | { 16 |

    @formSection.Name

    17 | @if (!string.IsNullOrEmpty(formSection.Description)) 18 | { 19 |

    @formSection.Description

    20 | } 21 | 22 | @foreach (var field in formSection.Fields) 23 | { 24 | 25 | } 26 | } 27 | 28 | 29 |
    30 | } 31 | @code { 32 | bool submitted; 33 | [Parameter] 34 | public required FormDto Content { get; set; } 35 | 36 | public void HandleValidSubmit(){} 37 | } 38 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormRenderer.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Models 2 | @namespace Marco.Forms.UI.Client.Components.Forms 3 | 4 | 6 | 7 | 8 | 9 | @foreach (var formSection in Form.Sections) 10 | { 11 |

    @formSection.Name

    12 | @if (!string.IsNullOrEmpty(formSection.Description)) 13 | { 14 |

    @formSection.Description

    15 | } 16 | 17 | @foreach (var field in formSection.Fields) 18 | { 19 | 20 | } 21 | } 22 | 23 | 24 |
    25 | 26 | @code { 27 | [Inject] public required FormStore Store { get; set; } 28 | 29 | [Parameter] public required FormDto Form { get; set; } 30 | 31 | private void HandleValidSubmit() 32 | { 33 | var formSubmissionId = Uuid.New(); 34 | var submission = new FormSubmission(formSubmissionId, Form.FormId, Uuid.New()) 35 | { 36 | Responses = [] 37 | }; 38 | 39 | // add all the responses to the submission 40 | foreach (var formSection in Form.Sections) 41 | { 42 | foreach (var q in formSection.Fields) 43 | { 44 | submission.Responses.Add(new FormResponse(Uuid.New(), formSubmissionId, q.FormFieldId!) 45 | { 46 | Value = q.Value 47 | }); 48 | } 49 | } 50 | 51 | Store.RecordSubmission(submission); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record FormResponse(string FormResponseId, string FormSubmissionId, string FormFieldId) 4 | { 5 | public FieldValue Value { get; set; } = new(); 6 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormResponsePage.razor: -------------------------------------------------------------------------------- 1 | @page "/forms/{formId}/responses" 2 | @using Marco.Forms.UI.Client.Components.Shared 3 | @using Microsoft.AspNetCore.Components.Sections 4 | @using Pure.Blazor.Components.Display 5 | @inject FormStore Store 6 | @rendermode InteractiveServer 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 |
    15 |
    16 |
    17 |
    18 | 19 |

    @selectedForm?.Name

    20 |
    21 | @if (selectedForm != null && selectedFormResponses.Any()) 22 | { 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @foreach (var response in selectedFormResponses) 32 | { 33 | var field = selectedForm.Sections.SelectMany(s => s.Fields).FirstOrDefault(f => f.FormFieldId == response.FormFieldId); 34 | 35 | 36 | 37 | 38 | } 39 | 40 |
    Field NameResponse
    @field?.Name@response.Value
    41 | } 42 | else if (selectedForm != null) 43 | { 44 |
    45 | 46 |

    No responses yet!

    47 |

    Share your form to start collecting responses. 48 |

    49 |
    50 | } 51 |
    52 |
    53 |
    54 | 55 | @code { 56 | private List? forms; 57 | private FormDto? selectedForm; 58 | private List selectedFormResponses = []; 59 | 60 | [Parameter] public required string FormId { get; set; } 61 | 62 | protected override async Task OnInitializedAsync() 63 | { 64 | forms = await Store.GetFormsAsync(); 65 | 66 | selectedForm = forms.FirstOrDefault(f => f.FormId == FormId); 67 | if (selectedForm != null) 68 | { 69 | var submissions = await Store.GetFormSubmissionsAsync(selectedForm.FormId); 70 | selectedFormResponses = submissions.SelectMany(p => p.Responses).ToList(); 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormSchema.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public class FormSchema 4 | { 5 | public string? Title { get; set; } 6 | public List Fields { get; set; } = new(); 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormSection.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record FormSection(string FormSectionId, string Name) 4 | { 5 | public string Name { get; set; } = Name; 6 | public string? Description { get; set; } 7 | public List Fields { get; set; } = []; 8 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | /// 4 | /// The status of a form being filled out. 5 | /// 6 | public enum FormStatus 7 | { 8 | Draft, 9 | Scheduled, 10 | InProgress, 11 | Paused, 12 | Completed, 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormStore.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Marco.Forms.UI.Client.Models; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Forms; 5 | 6 | public class FormStore(FormsClient client) 7 | { 8 | private List Forms { get; set; } = []; 9 | private List Submissions { get; set; } = []; 10 | 11 | public void RecordSubmission(FormSubmission submission) 12 | { 13 | Console.WriteLine(JsonSerializer.Serialize(submission)); 14 | Submissions.Add(submission); 15 | } 16 | 17 | public Task> GetFormSubmissionsAsync(string formId) 18 | { 19 | return Task.FromResult(Submissions.Where(s => s.FormId == formId).ToList()); 20 | } 21 | 22 | public async Task GetFormAsync(string id, CancellationToken ct = default) 23 | { 24 | Forms = await client.GetFormsAsync(ct); 25 | 26 | return Forms.FirstOrDefault(p => p.FormId == id); 27 | } 28 | 29 | public async Task> GetFormsAsync(CancellationToken ct = default) 30 | { 31 | Forms = await client.GetFormsAsync(ct); 32 | 33 | return Forms; 34 | } 35 | 36 | public Task CreateFormAsync(FormDto form) 37 | { 38 | if (string.IsNullOrWhiteSpace(form.FormId)) 39 | { 40 | form.FormId = Uuid.New(); 41 | } 42 | 43 | if (Forms.Any(f => f.FormId == form.FormId)) 44 | { 45 | throw new InvalidOperationException($"Form '{form.FormId}' already exists"); 46 | } 47 | 48 | Forms.Add(form); 49 | Console.WriteLine(JsonSerializer.Serialize(form)); 50 | return Task.FromResult(form); 51 | } 52 | 53 | public Task UpdateFormAsync(Form form) 54 | { 55 | var existingForm = Forms.FirstOrDefault(f => f.FormId == form.FormId); 56 | if (existingForm is null) 57 | { 58 | throw new InvalidOperationException($"Form '{form.FormId}' not found"); 59 | } 60 | 61 | var updatedForm = existingForm with 62 | { 63 | Name = form.Name, 64 | Version = ++form.Version, 65 | Status = form.Status, 66 | ScheduledDate = form.ScheduledDate, 67 | StartedDate = form.StartedDate, 68 | CompletedDate = form.CompletedDate, 69 | Description = form.Description, 70 | Notes = form.Notes, 71 | Sections = form.Sections 72 | }; 73 | Forms[Forms.IndexOf(existingForm)] = updatedForm; 74 | return Task.CompletedTask; 75 | } 76 | 77 | public async Task GeneratePublicLinkAsync(string formFormId) 78 | { 79 | var shareLink = await client.GenerateShareLinkAsync(formFormId); 80 | var form = Forms.FirstOrDefault(f => f.FormId == formFormId); 81 | if (form is null) 82 | { 83 | throw new InvalidOperationException($"Form '{formFormId}' not found"); 84 | } 85 | 86 | form.LinkIdentifier = shareLink.PublicLink; 87 | return shareLink.PublicLink; 88 | } 89 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormSubmission.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public record FormSubmission(string FormSubmissionId, string FormId, string UserId) 4 | { 5 | /// 6 | /// Which version of the form was submitted. 7 | /// 8 | public int FormVersion { get; set; } 9 | 10 | public DateTime SubmittedDate { get; set; } 11 | public List Responses { get; set; } = []; 12 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/FormsClient.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public class FormsClient 4 | { 5 | public Task GenerateShareLinkAsync(string formFormId) 6 | { 7 | return Task.FromResult(new ShareLink 8 | { 9 | FormId = formFormId, 10 | PublicLink = $"https://example.com/forms/{formFormId}/share" 11 | }); 12 | } 13 | 14 | public Task> GetFormsAsync(CancellationToken ct) 15 | { 16 | List forms = []; 17 | return Task.FromResult(forms); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/IFormSuggestionService.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Forms; 2 | 3 | public interface IFormSuggestionService 4 | { 5 | // Task> GetSuggestionsAsync(string fieldName); 6 | Task GenerateFieldAsync(string fieldPrompt); 7 | Task GenerateFieldAsJsonAsync(string fieldPrompt); 8 | 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/MarcoForm.razor: -------------------------------------------------------------------------------- 1 | @page "/forms/{FormId}/preview" 2 | @using Marco.Forms.UI.Client.Models 3 | @using Pure.Blazor.Components 4 | @rendermode InteractiveServer 5 | 6 |
    7 |
    8 | @if (FormModel is null) 9 | { 10 |

    Loading form...

    11 | } 12 | else 13 | { 14 | @FormModel.Name 15 | @if (submitted) 16 | { 17 |

    Thank you! We've recorded your response.

    18 | } 19 | else 20 | { 21 | 23 | 24 | 25 | 26 | @foreach (var formSection in FormModel.Sections) 27 | { 28 |

    @formSection.Name

    29 | @if (!string.IsNullOrEmpty(formSection.Description)) 30 | { 31 |

    @formSection.Description

    32 | } 33 | 34 | @foreach (var field in formSection.Fields) 35 | { 36 | 37 | } 38 | } 39 | 40 | 41 |
    42 | } 43 | } 44 |
    45 |
    46 | 47 | @code { 48 | bool submitted; 49 | [Inject] public required FormStore Store { get; set; } 50 | 51 | [SupplyParameterFromForm(FormName = "SearchForm")] 52 | public FormDto? FormModel { get; set; } 53 | 54 | [Parameter] public string? FormId { get; set; } 55 | 56 | protected override async Task OnInitializedAsync() 57 | { 58 | if (FormModel is null) 59 | { 60 | FormModel = (await Store.GetFormsAsync()).FirstOrDefault(p => p.FormId == FormId); 61 | } 62 | } 63 | 64 | private void HandleValidSubmit() 65 | { 66 | if (FormModel is null) 67 | { 68 | return; 69 | } 70 | 71 | var formSubmissionId = Uuid.New(); 72 | var submission = new FormSubmission(formSubmissionId, FormModel.FormId, Uuid.New()) 73 | { 74 | Responses = [] 75 | }; 76 | 77 | // add all the responses to the submission 78 | foreach (var formSection in FormModel.Sections) 79 | { 80 | foreach (var q in formSection.Fields) 81 | { 82 | submission.Responses.Add(new FormResponse(Uuid.New(), formSubmissionId, q.FormFieldId!) 83 | { 84 | Value = q.Value 85 | }); 86 | } 87 | } 88 | 89 | Store.RecordSubmission(submission); 90 | submitted = true; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/MoreButton.razor: -------------------------------------------------------------------------------- 1 |
    3 | 4 | 7 |
    8 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/RadioBlock.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Blocks; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Forms; 6 | 7 | public class RadioBlock : ComponentBase, IBlazorBlock 8 | { 9 | [Parameter] public required FieldSchema Field { get; set; } 10 | 11 | protected override void BuildRenderTree(RenderTreeBuilder builder) 12 | { 13 | var fragment = Render(); 14 | fragment(builder); 15 | } 16 | 17 | private RenderFragment Render() 18 | { 19 | return builder => 20 | { 21 | builder.OpenElement(0, "div"); 22 | builder.AddAttribute(1, "class", "form-radio flex items-center"); 23 | var optionOffset = 0; 24 | const int optionSize = 12; 25 | foreach (var option in Field.Options) 26 | { 27 | builder.OpenElement(2 + optionOffset, "input"); 28 | builder.AddAttribute(3 + optionOffset, "class", "form-radio-input"); 29 | builder.AddAttribute(4 + optionOffset, "type", "radio"); 30 | builder.AddAttribute(5 + optionOffset, "id", option.Label); 31 | builder.AddAttribute(6 + optionOffset, "name", option.Label); 32 | builder.AddAttribute(7 + optionOffset, "value", option.Value.TextValue); 33 | builder.AddAttribute(8 + optionOffset, "checked", option.Value.TextValue == Field.Value.TextValue); 34 | builder.AddAttribute(9 + optionOffset, "onchange", 35 | EventCallback.Factory.Create(this, OnChange)); 36 | builder.CloseElement(); 37 | 38 | builder.OpenElement(10 + optionOffset, "label"); 39 | builder.AddAttribute(11 + optionOffset, "class", 40 | "form-radio-label text-sm font-medium text-gray-700 px-2"); 41 | builder.AddAttribute(12 + optionOffset, "for", option.Label); 42 | builder.AddContent(13 + optionOffset, option.Label); 43 | builder.CloseElement(); 44 | 45 | optionOffset += optionSize; 46 | } 47 | 48 | builder.CloseElement(); 49 | }; 50 | } 51 | 52 | private void OnChange(ChangeEventArgs e) 53 | { 54 | if (e.Value is string value) 55 | { 56 | Field.Value.TextValue = value; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/SelectBlock.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Blocks; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | using Microsoft.AspNetCore.Components.Web; 5 | using Pure.Blazor.Components; 6 | 7 | namespace Marco.Forms.UI.Client.Components.Forms; 8 | 9 | public class SelectBlock : ComponentBase, IBlazorBlock 10 | { 11 | [Parameter] public required FieldSchema Field { get; set; } 12 | [Parameter] public bool ShowLabel { get; set; } 13 | [Parameter] public EventCallback FieldChanged { get; set; } 14 | 15 | protected override void BuildRenderTree(RenderTreeBuilder builder) 16 | { 17 | var fragment = Render(); 18 | fragment(builder); 19 | } 20 | 21 | private RenderFragment Render() 22 | { 23 | return builder => 24 | { 25 | builder.OpenElement(0, "div"); 26 | builder.AddAttribute(1, "class", "form-group"); 27 | if (ShowLabel) 28 | { 29 | builder.OpenElement(2, "label"); 30 | builder.AddAttribute(3, "for", Field.FormFieldId); 31 | builder.AddAttribute(4, "class", "text-[10px] pb-1 pt-2 font-bold uppercase text-gray-600"); 32 | builder.AddContent(5, Field.Name); 33 | builder.CloseElement(); 34 | } 35 | 36 | builder.OpenElement(6, "select"); 37 | builder.AddAttribute(7, "class", "border-1 rounded border-brand-600 px-1 py-0.5 text-sm"); 38 | builder.AddAttribute(8, "id", Field.FormFieldId); 39 | builder.AddAttribute(9, "name", Field.FormFieldId); 40 | builder.AddAttribute(10, "value", Field.Value); 41 | builder.AddAttribute(11, "onchange", 42 | EventCallback.Factory.Create(this, OnChange)); 43 | foreach (var option in Field.Options) 44 | { 45 | builder.OpenElement(12, "option"); 46 | 47 | var value = option.Value.ValueType switch 48 | { 49 | FieldValueType.Boolean => option.Value.BooleanValue.ToString(), 50 | FieldValueType.Number => option.Value.NumberValue.ToString(), 51 | FieldValueType.Text => option.Value.TextValue, 52 | FieldValueType.Date => option.Value.DateValue.ToString(), 53 | FieldValueType.Multi => option.Value.MultiValue?.ToString() ?? string.Empty, 54 | _ => option.Value.TextValue 55 | }; 56 | builder.AddAttribute(13, "value", value); 57 | builder.AddContent(14, option.Label); 58 | builder.CloseElement(); 59 | } 60 | 61 | builder.OpenRegion(15); 62 | builder.OpenElement(1, "div"); 63 | builder.AddAttribute(2, "class", "flex gap-2"); 64 | 65 | builder.OpenComponent>(3); 66 | builder.CloseComponent(); 67 | builder.OpenComponent 26 | 29 | 30 | } 31 | 32 | @code { 33 | [Parameter] public FieldSchema? Field { get; set; } 34 | [Parameter] public string? FieldJson { get; set; } 35 | [Parameter] public EventCallback OnAccept { get; set; } 36 | [Parameter] public EventCallback OnReject { get; set; } 37 | 38 | private readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions 39 | { 40 | WriteIndented = true, 41 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 42 | AllowTrailingCommas = true, 43 | UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip, 44 | ReadCommentHandling = JsonCommentHandling.Skip, 45 | PropertyNameCaseInsensitive = true, 46 | ReferenceHandler = ReferenceHandler.IgnoreCycles, 47 | }; 48 | 49 | protected override void OnParametersSet() 50 | { 51 | if (FieldJson is not null) 52 | { 53 | try 54 | { 55 | var newFields = JsonSerializer.Deserialize(FieldJson, jsonOptions); 56 | if (newFields == null || newFields.Length == 0) return; 57 | 58 | foreach (var newField in newFields) 59 | { 60 | newField.FormFieldId = Uuid.New(); 61 | } 62 | 63 | Field = newFields[0]; 64 | } 65 | catch (Exception e) 66 | { 67 | Console.WriteLine(e); 68 | } 69 | } 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/GridAutoColumns.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | /// 4 | /// Utilities for controlling the size of implicitly-created grid columns. 5 | /// 6 | public enum GridAutoColumns 7 | { 8 | None, 9 | Auto, 10 | Min, 11 | Max, 12 | Fr 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/GridAutoFlow.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | /// 4 | /// Utilities for controlling how elements in a grid are auto-placed. 5 | /// 6 | public enum GridAutoFlow 7 | { 8 | None, 9 | Row, 10 | Column, 11 | Dense, 12 | RowDense, 13 | ColumnDense 14 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/GridAutoRows.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | /// 4 | /// Utilities for controlling the size of implicitly-created grid rows. 5 | /// 6 | public enum GridAutoRows 7 | { 8 | None, 9 | Auto, 10 | Min, 11 | Max, 12 | Fr 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/GridLayoutOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record GridLayoutOptions 4 | { 5 | /// 6 | /// Use the grid-cols-* utilities to create grids with n equally sized columns. 7 | /// 8 | public int? TemplateColumns { get; set; } 9 | 10 | /// 11 | /// Use the grid-rows-* utilities to create grids with n equally sized rows. 12 | /// 13 | public int? TemplateRows { get; set; } 14 | 15 | /// 16 | /// Make an element span n rows. 17 | /// 18 | public int? RowSpan { get; set; } 19 | 20 | /// 21 | /// Use the row-start-* and row-end-* utilities to make an element start or end at the nth grid line. These can also be 22 | /// combined with the row-span-* utilities to span a specific number of rows. 23 | /// 24 | /// 25 | /// Note that CSS grid lines start at 1, not 0, so a full-height element in a 3-row grid would start at line 1 and end 26 | /// at line 4. 27 | /// 28 | public int? RowStart { get; set; } 29 | 30 | /// 31 | /// Use the row-start-* and row-end-* utilities to make an element start or end at the nth grid line. These can also be 32 | /// combined with the row-span-* utilities to span a specific number of rows. 33 | /// 34 | /// 35 | /// Note that CSS grid lines start at 1, not 0, so a full-height element in a 3-row grid would start at line 1 and end 36 | /// at line 4. 37 | /// 38 | public int? RowEnd { get; set; } 39 | 40 | /// 41 | /// Make an element span n columns. 42 | /// 43 | public int? ColumnSpan { get; set; } 44 | 45 | /// 46 | /// Use the col-start-* and col-end-* utilities to make an element start or end at the nth grid line. These can also be 47 | /// combined with the col-span-* utilities to span a specific number of columns. 48 | /// 49 | /// 50 | /// Note that CSS grid lines start at 1, not 0, so a full-width element in a 6-column grid would start at line 1 and 51 | /// end at line 7. 52 | /// 53 | public int? ColumnStart { get; set; } 54 | 55 | /// 56 | /// Use the col-start-* and col-end-* utilities to make an element start or end at the nth grid line. These can also be 57 | /// combined with the col-span-* utilities to span a specific number of columns. 58 | /// 59 | /// 60 | /// Note that CSS grid lines start at 1, not 0, so a full-width element in a 6-column grid would start at line 1 and 61 | /// end at line 7. 62 | /// 63 | public int? ColumnEnd { get; set; } 64 | 65 | /// 66 | /// Use the gap-x-* and gap-y-* utilities to change the gap between columns and rows independently. 67 | /// 68 | public int ColumnGap { get; set; } 69 | 70 | /// 71 | /// Use the gap-x-* and gap-y-* utilities to change the gap between columns and rows independently. 72 | /// 73 | public int RowGap { get; set; } 74 | 75 | /// 76 | /// Use the gap-* utilities to change the gap between both rows and columns in grid and flexbox layouts. 77 | /// 78 | public int Gap { get; set; } 79 | 80 | /// 81 | /// Use the grid-flow-* utilities to control how the auto-placement algorithm works for a grid layout. 82 | /// 83 | public GridAutoFlow AutoFlow { get; set; } 84 | 85 | /// 86 | /// Use the auto-cols-* utilities to control the size of implicitly-created grid columns. 87 | /// 88 | public GridAutoColumns AutoColumns { get; set; } 89 | 90 | /// 91 | /// Use the auto-rows-* utilities to control the size of implicitly-created grid rows. 92 | /// 93 | public GridAutoRows AutoRows { get; set; } 94 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/IPositioner.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public interface IPositioner 4 | { 5 | public (double X, double Y) GetPosition(); 6 | public void Update((double x, double y) newPosition); 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Layers.razor: -------------------------------------------------------------------------------- 1 | @using System.Collections.Frozen 2 | @using Pure.Blazor.Components.Icons 3 | @if (blockTree is not null) 4 | { 5 | @foreach (var (parentId, blocks) in blockTree) 6 | { 7 |
      8 |
    • 9 |
      10 | 11 |
      12 |
      @GetBlockLabel(parentId)
      13 |
    • 14 | @foreach (var block in blocks) 15 | { 16 |
    • @block.BlockLabel
    • 17 | } 18 |
    19 | } 20 | } 21 | else 22 | { 23 |

    Loading...

    24 | } 25 | 26 | @code { 27 | [Parameter] public FrozenSet Blocks { get; set; } = []; 28 | [Parameter] public EventCallback BlockSelected { get; set; } 29 | 30 | private FrozenDictionary>? blockTree; 31 | 32 | protected override void OnInitialized() 33 | { 34 | // blocks are stored as a DAG, print the tree to the log grouped by parent 35 | blockTree = Blocks 36 | .Where(p => p.ParentBlockIds.Any()) 37 | .OrderBy(p => p.BlockPosition) 38 | .GroupBy(p => p.ParentBlockIds?.FirstOrDefault()) 39 | .ToFrozenDictionary(p => p.Key ?? "", p => p.ToList()); 40 | 41 | // orders the parent blocks by their position in the tree 42 | blockTree = blockTree 43 | .OrderBy(p => Blocks.FirstOrDefault(b => b.BlockId == p.Key)?.BlockPosition) 44 | .ToFrozenDictionary(); 45 | } 46 | 47 | private string GetBlockLabel(string pageBlockId) 48 | { 49 | var block = Blocks.FirstOrDefault(b => b.BlockId == pageBlockId); 50 | return block?.BlockLabel ?? block?.BlockType ?? pageBlockId; 51 | } 52 | 53 | private void OnBlockSelected(string blockId) 54 | { 55 | BlockSelected.InvokeAsync(Blocks.SingleOrDefault(p => p.BlockId == blockId)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/MarcoCanvas.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Blocks 2 | @using Marco.Forms.UI.Client.Models 3 | @if (CanvasState.RootBlock is not null) 4 | { 5 | 6 | @if (CanvasState.Blocks.Count(p => p.BlockType != BlockTypes.Root) == 0) 7 | { 8 |

    Add a block to get started.

    9 | } 10 | 11 | @foreach (var block in TopLevelBlocks.OrderBy(p => p.BlockPosition)) 12 | { 13 | 18 | } 19 |
    20 | } 21 | else 22 | { 23 |
    24 |

    No blocks found

    25 |

    Add a block to get started.

    26 |
    27 | } 28 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/MarcoCanvas.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.JSInterop; 3 | 4 | namespace Marco.Forms.UI.Client.Components; 5 | 6 | public partial class MarcoCanvas( 7 | IJSRuntime jsRuntime 8 | ) 9 | { 10 | private IJSObjectReference? module; 11 | 12 | [Parameter, EditorRequired] public required string SiteId { get; set; } 13 | [Parameter, EditorRequired] public required MarcoCanvasState CanvasState { get; set; } 14 | [Parameter, EditorRequired] public required EventCallback CanvasStateChanged { get; set; } 15 | 16 | private IEnumerable TopLevelBlocks => CanvasState.RootBlock is null 17 | ? [] 18 | : CanvasState.Blocks.Where(p => p.ParentBlockIds?.Contains(CanvasState.RootBlock.BlockId) == true); 19 | 20 | protected override async Task OnAfterRenderAsync(bool firstRender) 21 | { 22 | if (!firstRender) 23 | { 24 | return; 25 | } 26 | 27 | module = await jsRuntime.InvokeAsync("import", 28 | "./Components/Marco/MarcoCanvas.razor.js"); 29 | await module.InvokeVoidAsync("initMarco"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/MarcoCanvas.razor.js: -------------------------------------------------------------------------------- 1 | export function initMarco() { 2 | // initResizables(); 3 | } 4 | 5 | function enableResize(resizable){ 6 | const handles = resizable.querySelectorAll('.resize-handle'); 7 | 8 | handles.forEach(handle => { 9 | handle.addEventListener('mousedown', (e) => { 10 | e.preventDefault(); 11 | 12 | const isTop = handle.classList.contains('top'); 13 | const isRight = handle.classList.contains('right'); 14 | const isBottom = handle.classList.contains('bottom'); 15 | const isLeft = handle.classList.contains('left'); 16 | 17 | const startX = e.clientX; 18 | const startY = e.clientY; 19 | const startWidth = parseInt(document.defaultView.getComputedStyle(resizable).width, 10); 20 | const startHeight = parseInt(document.defaultView.getComputedStyle(resizable).height, 10); 21 | 22 | function doDrag(event) { 23 | if (isRight) { 24 | resizable.style.width = startWidth + event.clientX - startX + 'px'; 25 | } else if (isLeft) { 26 | resizable.style.width = startWidth - (event.clientX - startX) + 'px'; 27 | } 28 | if (isBottom) { 29 | resizable.style.height = startHeight + event.clientY - startY + 'px'; 30 | } else if (isTop) { 31 | resizable.style.height = startHeight - (event.clientY - startY) + 'px'; 32 | } 33 | } 34 | 35 | function stopDrag() { 36 | document.removeEventListener('mousemove', doDrag); 37 | document.removeEventListener('mouseup', stopDrag); 38 | } 39 | 40 | document.addEventListener('mousemove', doDrag); 41 | document.addEventListener('mouseup', stopDrag); 42 | }); 43 | }); 44 | } 45 | 46 | function initResizables() { 47 | document.querySelectorAll('.resizable').forEach(resizable => { 48 | enableResize(resizable); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/MouseEventExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | 3 | namespace Marco.Forms.UI.Client.Components; 4 | 5 | public static class MouseEventExtensions 6 | { 7 | public static (double X, double Y) GetPosition(this MouseEventArgs args) 8 | { 9 | return (args.ClientX, args.ClientY); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/MoveBlockDialog.razor: -------------------------------------------------------------------------------- 1 |

    MoveBlockDialog

    2 | @Block.BlockPosition 3 | 4 | 5 | @code { 6 | [Parameter, EditorRequired] public required PageBlock Block { get; set; } 7 | [Parameter, EditorRequired] public required MarcoCanvasState CanvasState { get; set; } 8 | [Parameter, EditorRequired] public required EventCallback CanvasStateChanged { get; set; } 9 | 10 | private void PositionChanged(string s) 11 | { 12 | if (double.TryParse(s, out var position)) 13 | { 14 | var block = Block with { BlockPosition = position }; 15 | CanvasState.Sync(block); 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Node.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record Node 4 | { 5 | public string? Id { get; set; } 6 | public NodeType NodeType { get; init; } 7 | public string? NodeName { get; init; } 8 | public List? ChildNodes { get; init; } 9 | public Node? ParentNode { get; init; } 10 | public string? InnerText { get; init; } 11 | public string? InnerHTML { get; init; } 12 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/NodeType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public enum NodeType 4 | { 5 | Element, 6 | Text, 7 | Comment, 8 | // Add other types as needed 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/PageBlock.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Marco.Forms.UI.Client.Models; 3 | 4 | namespace Marco.Forms.UI.Client.Components; 5 | 6 | public record PageBlock 7 | { 8 | public required string BlockId { get; init; } 9 | public string? PageId { get; init; } 10 | 11 | /// 12 | /// The type of block, e.g. root, rich text, header, image, etc. 13 | /// 14 | public required string BlockType { get; init; } 15 | 16 | /// 17 | /// The position of the block in the page 18 | /// 19 | public double BlockPosition { get; set; } 20 | 21 | /// 22 | /// JSON serialized block data 23 | /// 24 | public BlockData? BlockData { get; set; } 25 | 26 | public int BlockDataVersion { get; set; } 27 | 28 | public HashSet ChildBlockIds { get; set; } = []; 29 | 30 | public HashSet ParentBlockIds { get; set; } = []; 31 | public string? Tag { get; set; } 32 | 33 | /// 34 | /// The formatting & styles of the block. 35 | /// 36 | public BlockFormatting? Formatting { get; set; } 37 | 38 | /// 39 | /// The layout of the block. 40 | /// 41 | public BlockLayout? Layout { get; set; } 42 | 43 | /// 44 | /// The label of the block. This is used for navigation and accessibility. 45 | /// This label is visible to the user and should be human-readable. It should be unique within the page. 46 | /// It is displayed in the block editor and in the block list/tree.k 47 | /// It is displayed in the block editor and in the block list/tree.k 48 | /// 49 | public string? BlockLabel { get; set; } 50 | 51 | public virtual bool Equals(PageBlock? other) 52 | { 53 | if (ReferenceEquals(null, other)) 54 | { 55 | return false; 56 | } 57 | 58 | if (ReferenceEquals(this, other)) 59 | { 60 | return true; 61 | } 62 | 63 | return BlockId == other.BlockId && BlockType == other.BlockType && BlockPosition.Equals(other.BlockPosition) && 64 | BlockData == other.BlockData && ChildBlockIds == other.ChildBlockIds && 65 | ParentBlockIds == other.ParentBlockIds; 66 | } 67 | 68 | public override int GetHashCode() => HashCode.Combine(BlockId, BlockType); 69 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Primitives/FontWeights.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Primitives; 2 | 3 | public enum FontWeights 4 | { 5 | Unset, 6 | Thin, 7 | ExtraLight, 8 | Light, 9 | Normal, 10 | Medium, 11 | SemiBold, 12 | Bold, 13 | ExtraBold, 14 | VeryBold 15 | } 16 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Primitives/Selection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Primitives; 4 | 5 | /// 6 | /// Represents the HTML Selection Event object. 7 | /// 8 | public record Selection 9 | { 10 | [JsonPropertyName("anchorNode")] public Node? AnchorNode { get; init; } 11 | [JsonPropertyName("anchorOffset")] public int AnchorOffset { get; init; } 12 | 13 | /// 14 | /// The type of selection: "None", "Caret" or "Range". 15 | /// 16 | public string? Type { get; set; } 17 | 18 | /// 19 | /// The direction of the selection: "forward", "backward" or "none". 20 | /// 21 | public string? Direction { get; init; } 22 | 23 | public Node? FocusNode { get; init; } 24 | public int FocusOffset { get; init; } 25 | public bool IsCollapsed { get; init; } 26 | public int RangeCount { get; init; } 27 | public SelectionRange? Range { get; set; } 28 | public DomRect? AnchorRect { get; init; } 29 | } 30 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/BoxPosition.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Properties; 2 | 3 | public enum BoxPosition 4 | { 5 | Unset, 6 | TopLeft, 7 | TopCenter, 8 | TopRight, 9 | MiddleLeft, 10 | MiddleCenter, 11 | MiddleRight, 12 | BottomLeft, 13 | BottomCenter, 14 | BottomRight 15 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/BoxPositionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Properties; 2 | 3 | public static class BoxPositionExtensions 4 | { 5 | public static string GetTransformCSS(this BoxPosition anchor) 6 | { 7 | string css(int xOff, int yOff) => $"transform: translate({xOff}%, {yOff}%)"; 8 | 9 | // By default; assuming no other stylings, css treats the top left as an anchor 10 | // To treat the bottom as the anchor, we use -100% y 11 | // To treat the right as the anchor, we use -100% x 12 | const int None = 0; 13 | const int Half = -50; 14 | const int Whole = -100; 15 | return anchor switch 16 | { 17 | BoxPosition.TopLeft => css(None, None), 18 | BoxPosition.MiddleLeft => css(None, Half), 19 | BoxPosition.BottomLeft => css(None, Whole), 20 | 21 | BoxPosition.TopCenter => css(Half, None), 22 | BoxPosition.MiddleCenter => css(Half, Half), 23 | BoxPosition.BottomCenter => css(Half, Whole), 24 | 25 | BoxPosition.TopRight => css(Whole, None), 26 | BoxPosition.MiddleRight => css(Whole, Half), 27 | BoxPosition.BottomRight => css(Whole, Whole), 28 | 29 | _ => css(None, None), 30 | }; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/PositionAnchor.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Properties; 2 | 3 | public enum PositionAnchor 4 | { 5 | Unset, 6 | Start, 7 | End 8 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/SectionProperties.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Blocks 2 | @using Marco.Forms.UI.Client.Components.Primitives 3 | @using Marco.Forms.UI.Client.Models 4 | @using Pure.Blazor.Components 5 |
    6 | Background color 7 |
    8 | 9 | 10 | 11 | @backgroundColor.ToString() 12 |
    13 | @if (backgroundColorPickerVisible) 14 | { 15 |
    16 |
    17 | @foreach (var color in Enum.GetValues() 18 | .Where(p => 19 | // exclude black/white/transparent 20 | p.ToString().EndsWith("0") && 21 | // exclude the lowest and highest shades 22 | // this is mostly a visual appearance thing and my skepticism on them being required 23 | !p.ToString().EndsWith("50") && !p.ToString().EndsWith("950")) 24 | // reverse the order to show the darkest shades first 25 | .Reverse()) 26 | { 27 | 28 | } 29 |
    30 |
    31 | } 32 | 33 |
    34 | 35 | @code { 36 | private bool backgroundColorPickerVisible = false; 37 | [Parameter] public required BlockFormatting Formatting { get; set; } 38 | [Parameter] public EventCallback FormattingChanged { get; set; } 39 | private TailwindColor backgroundColor = TailwindColor.White; 40 | 41 | private async Task BackgroundColorChanged(TailwindColor color) 42 | { 43 | backgroundColor = color; 44 | Formatting.BackgroundColor = TailwindBackgroundColorMapper.MapEnumToTailwindBackgroundColorClass(color); 45 | await FormattingChanged.InvokeAsync(Formatting); 46 | } 47 | 48 | private async Task PaddingChanged(Spacing padding) 49 | { 50 | Formatting.Padding = new () 51 | { 52 | Bottom = padding.Bottom, 53 | Left = padding.Left, 54 | Right = padding.Right, 55 | Top = padding.Top, 56 | Unit = padding.Unit 57 | }; 58 | await FormattingChanged.InvokeAsync(Formatting); 59 | } 60 | 61 | private async Task MarginChanged(Spacing spacing) 62 | { 63 | Formatting.Margin = new() 64 | { 65 | Bottom = spacing.Bottom, 66 | Left = spacing.Left, 67 | Right = spacing.Right, 68 | Top = spacing.Top, 69 | Unit = spacing.Unit 70 | }; 71 | await FormattingChanged.InvokeAsync(Formatting); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/SpacingAdjuster.razor: -------------------------------------------------------------------------------- 1 | Spacing (rem) 2 |
    3 |
    4 |
    5 | 7 |
    8 | 10 |
    13 |
    16 |
    17 |
    18 |
    19 | 21 |
    22 | 24 |
    25 |
    26 |
    @currentMode
    27 |
    28 | 29 | @code { 30 | [Parameter] public Spacing Margin { get; set; } = new(); 31 | [Parameter] public EventCallback MarginChanged { get; set; } 32 | 33 | [Parameter] public Spacing Padding { get; set; } = new(); 34 | [Parameter] public EventCallback PaddingChanged { get; set; } 35 | 36 | [Parameter] public CssUnit CssUnit { get; set; } 37 | 38 | private string currentMode = "Padding"; 39 | 40 | private Spacing values = new(0, 0, 0, 0); 41 | protected override void OnInitialized() 42 | { 43 | currentMode = "Padding"; 44 | values = Padding; 45 | } 46 | 47 | protected override void OnParametersSet() 48 | { 49 | UpdateValues(); 50 | 51 | } 52 | 53 | private void UpdateValues() 54 | { 55 | values = currentMode == "Padding" ? Padding : Margin; 56 | } 57 | 58 | private void ToggleMode() 59 | { 60 | currentMode = currentMode == "Padding" ? "Margin" : "Padding"; 61 | UpdateValues(); 62 | } 63 | 64 | private void PaddingMode() 65 | { 66 | currentMode = "Padding"; 67 | } 68 | 69 | private void MarginMode() 70 | { 71 | currentMode = "Margin"; 72 | } 73 | 74 | private async Task ChangePaddingAsync() 75 | { 76 | await PaddingChanged.InvokeAsync(values); 77 | } 78 | 79 | private async Task ChangeMarginAsync() 80 | { 81 | await MarginChanged.InvokeAsync(values); 82 | } 83 | 84 | private async Task ValueChange(Spacing v) 85 | { 86 | values = v; 87 | 88 | if (currentMode == "Padding") 89 | { 90 | await ChangePaddingAsync(); 91 | } 92 | else 93 | { 94 | await ChangeMarginAsync(); 95 | } 96 | } 97 | 98 | private static int InputObjToNum(object? o) 99 | { 100 | var str = (string?)o; 101 | return InputStrToNum(str); 102 | } 103 | 104 | private static int InputStrToNum(string? val) 105 | { 106 | return string.IsNullOrWhiteSpace(val) ? 0 : int.Parse(val); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/ResizableContainer.razor: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @* *@ 14 | 15 | @* *@ 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | @ChildContent 27 |
    28 | 29 | @code { 30 | [Parameter, EditorRequired] public required RenderFragment ChildContent { get; set; } 31 | 32 | [Parameter] public string BackgroundClass { get; set; } = "bg-transparent"; 33 | 34 | [Parameter] public double Width { get; set; } = 100; 35 | 36 | [Parameter] public double Height { get; set; } = 100; 37 | 38 | [Parameter] public bool Enabled { get; set; } 39 | private string Styles = ""; 40 | // private string Styles => Width != 0 || Height != 0 ? $"width: {Width}px; height: {Height}px;" : ""; 41 | private string Display => Enabled ? "" : "hidden"; 42 | } 43 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Samples/EmbedFormGPT.razor: -------------------------------------------------------------------------------- 1 | @page "/FormGPT" 2 | @using Marco.Forms.UI.Client.Components.Forms 3 | @using Pure.Blazor.Components 4 | @rendermode InteractiveServer 5 |
    6 |

    Embedded FormGPT

    7 | 8 | 9 | 10 |
    11 | 12 | 13 | @if (Form is not null) 14 | { 15 | 16 | } 17 | 18 | 19 | 20 | @code { 21 | public bool IsOpen { get; set; } 22 | public FormDto? Form { get; set; } 23 | 24 | public void NewForm(FormDto form) 25 | { 26 | Form = form; 27 | IsOpen = true; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Samples/FormLoader.razor: -------------------------------------------------------------------------------- 1 | @using Marco.Forms.UI.Client.Components.Forms 2 | @inject FormStore FormStore 3 | 4 | @if (Form is not null) 5 | { 6 | 7 | } 8 | else 9 | { 10 |

    Loading...

    11 | } 12 | 13 | @code { 14 | [Parameter] public required string FormId { get; set; } 15 | 16 | public FormDto? Form { get; set; } 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | Form = await FormStore.GetFormAsync(FormId); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Samples/MyDataCapturePage.razor: -------------------------------------------------------------------------------- 1 | @page "/MyDataCapturePage" 2 |
    3 |

    Patient Intake Form

    4 | 5 | 6 | 7 |
    8 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Samples/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.FluentUI.AspNetCore.Components 2 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/ScreenPositioner.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Primitives; 2 | using Microsoft.AspNetCore.Components.Web; 3 | 4 | namespace Marco.Forms.UI.Client.Components; 5 | 6 | public record ScreenPositioner : IPositioner 7 | { 8 | public static ScreenPositioner New(MouseEventArgs args) 9 | { 10 | return new(args.GetPosition()); 11 | } 12 | 13 | public static ScreenPositioner New(Selection selection) 14 | { 15 | return new(selection.GetPosition()); 16 | } 17 | public ScreenPositioner((double x, double y)? position = null) 18 | { 19 | Position = position ?? (0, 0); 20 | } 21 | 22 | private (double X, double Y) Position { get; set; } 23 | 24 | public void Update((double x, double y) newPosition) 25 | { 26 | Position = newPosition; 27 | } 28 | 29 | public (double X, double Y) GetPosition() 30 | { 31 | return Position; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/SelectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Primitives; 2 | 3 | namespace Marco.Forms.UI.Client.Components; 4 | 5 | public static class SelectionExtensions 6 | { 7 | /// 8 | /// Returns position based on the editor cursor 9 | /// 10 | public static (double X, double Y) GetPosition(this Selection? selection) 11 | { 12 | return selection?.AnchorRect is null ? (0, 0) : (selection.AnchorRect.X, selection.AnchorRect.Y); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/SelectionRange.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record SelectionRange 4 | { 5 | public int StartOffset { get; set; } 6 | public int EndOffset { get; set; } 7 | public bool Collapsed { get; set; } 8 | public Node? StartContainer { get; set; } 9 | public Node? EndContainer { get; set; } 10 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/AppBar.razor: -------------------------------------------------------------------------------- 1 | @using Pure.Blazor.Components 2 | 3 | 4 | 5 | 8 | 11 | 12 | Home 13 | 14 |
    15 | 18 | Forms 19 | 20 |
    21 | 22 | @if (Actions is not null) 23 | { 24 | @Actions 25 | } 26 |
    27 | 28 | @code { 29 | [Parameter] public RenderFragment? Actions { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/DefaultAppBar.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 |
    7 |
    8 | @code { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/DefaultErrorBoundary.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Shared; 4 | 5 | public class DefaultErrorBoundary(ILogger logger) : ErrorBoundary 6 | { 7 | protected override Task OnErrorAsync(Exception ex) 8 | { 9 | logger.LogError(ex, "An error occurred in a component"); 10 | return Task.CompletedTask; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/Error.razor: -------------------------------------------------------------------------------- 1 | 2 | @ChildContent 3 | 4 | 5 | @code { 6 | 7 | [Parameter] public RenderFragment? ChildContent { get; set; } 8 | [Inject] public required ILogger Logger { get; set; } 9 | 10 | public void ProcessError(Exception ex) 11 | { 12 | Logger.LogError(ex, "An error occurred"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/FullPageComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.FluentUI.AspNetCore.Components 2 | @using Microsoft.AspNetCore.Components.Sections 3 | @using Marco.Forms.UI.Client.Components.Controls 4 | @using Marco.Forms.UI.Client.Components.Properties 5 | @using Marco.Forms.UI.Client.Models 6 | @using Pure.Blazor.Components 7 | @layout FullPageLayout 8 |
    9 |
    10 |
    11 | This page is best viewed on a computer with resolution > 1024px. 12 | 13 |
    14 | 15 |
    16 | 17 |
    18 | @if (ShowStartColumn) 19 | { 20 | 21 | @* *@ 23 | @* *@ 24 | @* *@ 25 | } 26 |
    27 | @ChildContent 28 |
    29 | 30 | @if (ShowEndColumn) 31 | { 32 | 34 | 35 | 36 | } 37 | @* @if (IsDocked) *@ 38 | @* { *@ 39 | @* *@ 45 | @* } *@ 46 | @* else *@ 47 | @* { *@ 48 | @* *@ 50 | @* *@ 51 | @* *@ 52 | @* } *@ 53 |
    54 |
    55 | 56 | 57 | 58 | 59 | 60 | 61 | @code { 62 | internal static object StartColSection = new(); 63 | internal static object EndColSection = new(); 64 | internal static object TopBarSection = new(); 65 | 66 | private AccountDetails? _accountDetails; 67 | [Parameter] public bool ShowStartColumn { get; set; } = true; 68 | 69 | [Parameter] public bool ShowEndColumn { get; set; } = true; 70 | 71 | [Parameter] public RenderFragment? ChildContent { get; set; } 72 | public bool IsDocked { get; set; } = true; 73 | [Inject] public required ServerClient ServerClient { get; set; } 74 | [Inject] public required ILogger Logger { get; set; } 75 | 76 | protected override async Task OnInitializedAsync() 77 | { 78 | try 79 | { 80 | _accountDetails = await GetAccountDetails(); 81 | } 82 | catch (Exception e) 83 | { 84 | Logger.LogError(e, "Failed to get account details, defaulting to ghost account."); 85 | _accountDetails = new AccountDetails 86 | { 87 | AccountId = "ghost", 88 | AccountStatus = (int)AccountStatus.Active 89 | }; 90 | } 91 | } 92 | 93 | private async Task GetAccountDetails() 94 | { 95 | return await ServerClient.GetAccountDetailsAsync(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/FullPageLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
    4 | @Body 5 |
    6 | 7 |
    8 | An unhandled error has occurred. 9 | Reload 10 | 🗙 11 |
    12 | 13 | @code { 14 | [Inject] public required NavigationManager Nav { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/HoverToggle.razor: -------------------------------------------------------------------------------- 1 | 2 | @ChildContent 3 | @if (IsVisible && HoverContent != null) 4 | { 5 |
    6 | @HoverContent 7 |
    8 | } 9 |
    10 | 11 | @code { 12 | [Parameter] public bool IsVisible { get; set; } = false; 13 | 14 | // The main content to hover over 15 | [Parameter] public RenderFragment? ChildContent { get; set; } 16 | 17 | // Content to render when hover state is active 18 | [Parameter] public RenderFragment? HoverContent { get; set; } 19 | 20 | private void HandleHover(bool hovering) 21 | { 22 | IsVisible = hovering; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/Overlay.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Shared; 5 | 6 | public class Overlay : ComponentBase 7 | { 8 | [Parameter] 9 | public EventCallback OnClick { get; set; } 10 | 11 | /// 12 | /// If true, the overlay dims the background. If false, the overlay is transparent. 13 | /// 14 | [Parameter] 15 | public bool Dim { get; set; } 16 | 17 | protected override void BuildRenderTree(RenderTreeBuilder builder) 18 | { 19 | var bgClass = Dim ? "bg-black opacity-25" : "bg-transparent"; 20 | builder.OpenElement(0, "div"); 21 | builder.AddAttribute(1, "class", $"fixed inset-0 {bgClass} z-30"); 22 | builder.AddAttribute(2, "onclick", OnClick); 23 | builder.CloseElement(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/PopupMenu.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace Marco.Forms.UI.Client.Components.Shared; 5 | 6 | public class PopupMenu : ComponentBase 7 | { 8 | [Parameter] public Pressable? Trigger { get; set; } 9 | 10 | [Parameter] 11 | public Type? Component { get; set; } 12 | 13 | [Parameter, EditorRequired] public required RenderFragment Menu { get; set; } 14 | 15 | [Parameter] 16 | public bool Dim { get; set; } 17 | 18 | private bool isVisible; 19 | 20 | protected override void BuildRenderTree(RenderTreeBuilder builder) 21 | { 22 | //Foo.Element = "a"; 23 | if (Component is not null) 24 | { 25 | Trigger = new Pressable(b => { b.OpenComponent(0, Component); b.CloseComponent(); }); 26 | } 27 | 28 | if (Trigger is not null) 29 | { 30 | Trigger.OnPress = EventCallback.Factory.Create(this, ToggleVisibility); 31 | } 32 | 33 | // overlay 34 | if (isVisible) 35 | { 36 | builder.OpenComponent(0); 37 | builder.AddAttribute(1, nameof(Overlay.OnClick), 38 | EventCallback.Factory.Create(this, () => { isVisible = false; })); 39 | builder.AddAttribute(2, nameof(Overlay.Dim), Dim); 40 | builder.CloseComponent(); 41 | } 42 | 43 | // menu 44 | builder.OpenRegion(10); 45 | builder.OpenElement(0, "div"); 46 | builder.AddAttribute(1, "class", "relative z-40"); 47 | if (Trigger is not null) 48 | { 49 | builder.AddContent(2, Trigger.CreateFragment); 50 | } 51 | 52 | if (isVisible) 53 | { 54 | // anchor-name: --my-anchor 55 | builder.OpenElement(3, "div"); 56 | builder.AddAttribute(4, "class", "absolute flex gap-3 items-center cursor-pointer text-gray-900"); 57 | builder.AddAttribute(5, "style", ""); 58 | 59 | // if a menu is clicked, it should close the menu 60 | builder.AddAttribute(6, "onclick", EventCallback.Factory.Create(this, ToggleVisibility)); 61 | builder.AddContent(7, Menu); 62 | builder.CloseElement(); 63 | } 64 | 65 | builder.CloseElement(); 66 | builder.CloseRegion(); 67 | } 68 | 69 | private void ToggleVisibility() 70 | { 71 | isVisible = !isVisible; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/Pressable.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Pure.Blazor.Components; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Shared; 6 | 7 | public record Pressable 8 | { 9 | public string? Content { get; set; } 10 | 11 | public Pressable(RenderFragment childContent, string cssClass = "") 12 | { 13 | ChildContent = childContent; 14 | CssClass = cssClass; 15 | } 16 | 17 | public Pressable(string content, string? cssClass = default, string? cssStyle = default, EventCallback onPress = default) 18 | { 19 | this.Content = content; 20 | CssClass = cssClass; 21 | CssStyle = cssStyle; 22 | OnPress = onPress; 23 | } 24 | 25 | [Parameter] 26 | public RenderFragment? ChildContent { get; set; } 27 | 28 | [Parameter] 29 | public string Element { get; set; } = "button"; 30 | 31 | public EventCallback OnPress { get; set; } 32 | 33 | public Action? OnPressAction { get; set; } 34 | 35 | [Parameter] 36 | public string? CssClass { get; set; } 37 | 38 | [Parameter] 39 | public string? CssStyle { get; set; } 40 | 41 | public RenderFragment CreateFragment => builder => 42 | { 43 | builder.OpenElement(0, Element); 44 | builder.AddAttributeIfNotNullOrEmpty(1, "class", CssClass); 45 | builder.AddAttributeIfNotNullOrEmpty(2, "style", CssStyle); 46 | builder.AddAttribute(3, "onclick", EventCallback.Factory.Create(this, Pressed)); 47 | if (ChildContent is not null) 48 | { 49 | builder.AddContent(4, ChildContent); 50 | } 51 | else 52 | { 53 | builder.AddContent(5, Content); 54 | } 55 | 56 | builder.CloseElement(); 57 | }; 58 | 59 | private async Task Pressed(MouseEventArgs e) 60 | { 61 | await OnPress.InvokeAsync(e); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/ProfileButton.razor: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 | 6 | 8 | 9 |
    10 |
    11 |

    @_username

    12 |

    View profile

    13 |
    14 |
    15 |
    16 | 17 | @code { 18 | private string _username = "ghost"; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | @code { 3 | protected override void OnInitialized() 4 | { 5 | // NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/TopToolbar.razor: -------------------------------------------------------------------------------- 1 | 
    2 | @ChildContent 3 |
    4 | 5 | @code { 6 | [Parameter] 7 | public RenderFragment? ChildContent { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Shared/TrackHover.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Components.Shared; 4 | 5 | public class TrackHover : IComponent 6 | { 7 | private RenderHandle renderHandle; 8 | private bool currentHoverState; 9 | private bool pinned; 10 | 11 | [Parameter] public RenderFragment? ChildContent { get; set; } 12 | [Parameter] public EventCallback OnHoverChanged { get; set; } 13 | 14 | public void Attach(RenderHandle handle) 15 | { 16 | this.renderHandle = handle; 17 | } 18 | 19 | public Task SetParametersAsync(ParameterView parameters) 20 | { 21 | parameters.SetParameterProperties(this); 22 | Render(); 23 | return Task.CompletedTask; 24 | } 25 | 26 | private void Render() 27 | { 28 | renderHandle.Render(builder => 29 | { 30 | builder.OpenElement(0, "div"); 31 | builder.AddAttribute(1, "onmouseover", EventCallback.Factory.Create(this, () => HandleHover(true))); 32 | builder.AddAttribute(2, "onmouseout", EventCallback.Factory.Create(this, () => HandleHover(false))); 33 | builder.AddAttribute(3, "onclick", EventCallback.Factory.Create(this, HandleHoverClick)); 34 | builder.AddContent(4, ChildContent); 35 | builder.CloseElement(); 36 | }); 37 | } 38 | 39 | private async Task HandleHoverClick() 40 | { 41 | // if the user clicks on the hover component, it should pin the hover state 42 | // so that it doesn't change until the user clicks again 43 | // if the user first hovered, then clicked, it should stay open 44 | if (currentHoverState && !pinned) 45 | { 46 | pinned = true; 47 | return; 48 | } 49 | 50 | var nextHoverState = !currentHoverState; 51 | pinned = nextHoverState; 52 | 53 | currentHoverState = nextHoverState; 54 | if (OnHoverChanged.HasDelegate) 55 | { 56 | await OnHoverChanged.InvokeAsync(nextHoverState); 57 | } 58 | } 59 | 60 | private async Task HandleHover(bool isHovering) 61 | { 62 | if (pinned) 63 | { 64 | return; 65 | } 66 | 67 | currentHoverState = isHovering; 68 | if (OnHoverChanged.HasDelegate) 69 | { 70 | await OnHoverChanged.InvokeAsync(isHovering); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Spacing.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public record Spacing() 4 | { 5 | public Spacing(double top, double right, double bottom, double left) : this() 6 | { 7 | Top = top; 8 | Right = right; 9 | Bottom = bottom; 10 | Left = left; 11 | } 12 | 13 | public Spacing(double all) : this() 14 | { 15 | Top = all; 16 | Right = all; 17 | Bottom = all; 18 | Left = all; 19 | } 20 | 21 | public double Top { get; set; } = 0; 22 | public double Right { get; set; } = 0; 23 | public double Bottom { get; set; } = 0; 24 | public double Left { get; set; } = 0; 25 | public CssUnit Unit { get; set; } = CssUnit.Rem; 26 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/TextAlign.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components; 2 | 3 | public enum TextAlign 4 | { 5 | Unset, 6 | Left, 7 | Center, 8 | Right, 9 | Justify, 10 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Trees/TreeItemType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Components.Trees; 2 | 3 | public enum TreeItemType 4 | { 5 | File = 1, 6 | Folder = 2 7 | } 8 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Trees/TreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Marco.Forms.UI.Client.Models; 3 | using Pure.Blazor.Components; 4 | 5 | namespace Marco.Forms.UI.Client.Components.Trees; 6 | 7 | public class TreeViewItem 8 | { 9 | public string Id { get; set; } = Uuid.New(); 10 | public string? Name { get; set; } 11 | public int Level { get; set; } 12 | public TreeItemType Type { get; set; } 13 | public string? ParentId { get; set; } 14 | public string? ParentName { get; set; } 15 | public List Children { get; set; } = []; 16 | public bool IsCollapsed { get; set; } = true; 17 | public bool IsSelected { get; set; } = false; 18 | public bool HasChildren => Type == TreeItemType.Folder && Children.Any(); 19 | public bool PromptRename { get; set; } = false; 20 | public string Content { get; set; } = ""; 21 | 22 | [JsonIgnore] public PureInput? Reference { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Wizard.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 |
    5 |
    6 | @ChildContent 7 |
    8 | @if (ShowStepIndicators) 9 | { 10 |
      11 | @for (int i = 0; i < Steps.Count; i++) 12 | { 13 |
    1. 14 | @Steps[i].Title 15 |
    2. 16 | } 17 |
    18 | } 19 |
    20 | 21 | @if (CurrentStepIndex < Steps.Count) 22 | { 23 |
    24 | @Steps[CurrentStepIndex].ChildContent 25 |
    26 | } 27 | 28 | 29 |
    30 | @if (CurrentStepIndex > 0) 31 | { 32 | 33 | } 34 | @if (CurrentStepIndex < Steps.Count - 1) 35 | { 36 | 37 | } 38 | else 39 | { 40 | 41 | } 42 |
    43 |
    44 |
    45 | 46 | @code { 47 | [Parameter] public RenderFragment? ChildContent { get; set; } 48 | [Parameter] public bool ShowStepIndicators { get; set; } 49 | 50 | internal List Steps { get; set; } = []; 51 | 52 | private int CurrentStepIndex { get; set; } = 0; 53 | 54 | // Expose event for the outside to know when wizard is complete 55 | [Parameter] public EventCallback OnFinish { get; set; } 56 | 57 | public void RegisterStep(WizardStepDefinition step) 58 | { 59 | Steps.Add(step); 60 | StateHasChanged(); 61 | } 62 | 63 | private void NextStep() 64 | { 65 | if (CurrentStepIndex < Steps.Count - 1) 66 | { 67 | CurrentStepIndex++; 68 | } 69 | } 70 | 71 | private void PreviousStep() 72 | { 73 | if (CurrentStepIndex > 0) 74 | { 75 | CurrentStepIndex--; 76 | } 77 | } 78 | 79 | private async Task FinishWizard() 80 | { 81 | await OnFinish.InvokeAsync(null); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/WizardStep.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | 3 | @code { 4 | [CascadingParameter] public Wizard? ParentWizard { get; set; } 5 | 6 | /// 7 | /// The title of the step 8 | /// 9 | [Parameter] public string? Title { get; set; } 10 | 11 | /// 12 | /// Any content you want to display in the step 13 | /// 14 | [Parameter] public RenderFragment? ChildContent { get; set; } 15 | 16 | protected override void OnInitialized() 17 | { 18 | Console.WriteLine("WizardStep initialized"); 19 | ParentWizard?.RegisterStep(new WizardStepDefinition 20 | { 21 | Title = Title, 22 | ChildContent = ChildContent 23 | }); 24 | Console.WriteLine("Step registered: {0}", ParentWizard is not null); 25 | } 26 | 27 | public void Dispose() 28 | { 29 | // Optionally handle cleanup or deregistration 30 | // if you want to remove steps dynamically 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/WizardStepDefinition.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Components; 4 | 5 | public record WizardStepDefinition 6 | { 7 | public string? Title { get; set; } 8 | public RenderFragment? ChildContent { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 2 | USER $APP_UID 3 | WORKDIR /app 4 | EXPOSE 8080 5 | EXPOSE 8081 6 | 7 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 8 | ARG BUILD_CONFIGURATION=Release 9 | WORKDIR /src 10 | COPY ["Marco.Forms.UI/Marco.Forms.UI.Client/Marco.Forms.UI.Client.csproj", "Marco.Forms.UI/Marco.Forms.UI.Client/"] 11 | RUN dotnet restore "Marco.Forms.UI/Marco.Forms.UI.Client/Marco.Forms.UI.Client.csproj" 12 | COPY . . 13 | WORKDIR "/src/Marco.Forms.UI/Marco.Forms.UI.Client" 14 | RUN dotnet build "./Marco.Forms.UI.Client.csproj" -c $BUILD_CONFIGURATION -o /app/build 15 | 16 | FROM build AS publish 17 | ARG BUILD_CONFIGURATION=Release 18 | RUN dotnet publish "./Marco.Forms.UI.Client.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false 19 | 20 | FROM base AS final 21 | WORKDIR /app 22 | COPY --from=publish /app/publish . 23 | ENTRYPOINT ["dotnet", "Marco.Forms.UI.Client.dll"] 24 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Marco.Forms.UI.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | Default 9 | Linux 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | .dockerignore 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/AccountDetails.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record AccountDetails 4 | { 5 | public required string AccountId { get; set; } 6 | public required int AccountStatus { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/AccountStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public enum AccountStatus 4 | { 5 | Inactive, 6 | Active 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | [JsonPolymorphic(TypeDiscriminatorPropertyName = "BlockType")] 6 | [JsonDerivedType(typeof(SectionBlockData), typeDiscriminator: BlockTypes.Section)] 7 | [JsonDerivedType(typeof(ContentBlockData), typeDiscriminator: BlockTypes.ContentEditable)] 8 | [JsonDerivedType(typeof(RootBlock), typeDiscriminator: BlockTypes.Root)] 9 | [JsonDerivedType(typeof(BlockData), typeDiscriminator: BlockTypes.Block)] 10 | [JsonDerivedType(typeof(NavigationBlockData), typeDiscriminator: BlockTypes.Navigation)] 11 | [JsonDerivedType(typeof(ListBlockData), typeDiscriminator: BlockTypes.List)] 12 | [JsonDerivedType(typeof(LinkBlockData), typeDiscriminator: BlockTypes.Link)] 13 | public record BlockData 14 | { 15 | public required string BlockId { get; set; } 16 | public required string BlockType { get; set; } 17 | public int Version { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockDefaults.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public static class BlockDefaults 6 | { 7 | public const int Version = 1; 8 | 9 | public static BlockData CreateBlockData(PageBlock block) 10 | { 11 | return block.BlockType switch 12 | { 13 | BlockTypes.ContentEditable => new ContentBlockData 14 | { 15 | BlockId = block.BlockId, 16 | BlockType = block.BlockType, 17 | Version = block.BlockDataVersion, 18 | Text = "Write something here", 19 | }, 20 | BlockTypes.Section => new SectionBlockData 21 | { 22 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion 23 | }, 24 | BlockTypes.Root => new RootBlock 25 | { 26 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion 27 | }, 28 | BlockTypes.List => new ListBlockData 29 | { 30 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion 31 | }, 32 | BlockTypes.Navigation => new NavigationBlockData 33 | { 34 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion 35 | }, 36 | BlockTypes.Block => new BlockData 37 | { 38 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion 39 | }, 40 | BlockTypes.Link => new LinkBlockData 41 | { 42 | BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion, 43 | Text = "Link text", 44 | Url = "https://example.com" 45 | }, 46 | _ => new() { BlockId = block.BlockId, BlockType = block.BlockType, Version = block.BlockDataVersion } 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record BlockDefinition 4 | { 5 | public required string Id { get; set; } 6 | public required string Name { get; set; } 7 | public required string Description { get; set; } 8 | public required string Icon { get; set; } 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockFontFamily.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public enum BlockFontFamily 4 | { 5 | Sans, 6 | Serif, 7 | Mono 8 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockFormatting.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public record BlockFormatting 6 | { 7 | public TextAlign TextAlign { get; set; } 8 | public string? FontFamily { get; set; } 9 | public double FontSizeV2 { get; set; } 10 | public CssUnit FontSizeUnit { get; set; } 11 | public string? FontWeight { get; set; } 12 | public string? TextColor { get; set; } 13 | public string? BackgroundColor { get; set; } 14 | public string? Tracking { get; set; } 15 | public Spacing Padding { get; set; } = new(); 16 | public Spacing Margin { get; set; } = new(); 17 | public Border? Border { get; set; } 18 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockLayout.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public record BlockLayout 6 | { 7 | public int Version { get; set; } 8 | public BlockLayoutType LayoutType { get; set; } 9 | public GridLayoutOptions? GridLayoutOptions { get; set; } 10 | public FixedLayoutOptions? FixedLayoutOptions { get; set; } 11 | public Spacing Padding { get; set; } = new(); 12 | public Spacing Margin { get; set; } = new(); 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockLayoutExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public static class BlockLayoutExtensions 4 | { 5 | public static string ToCssClass(this BlockLayout layout) 6 | { 7 | return $"{layout.GetCssClass()}"; //{layout.GridLayoutOptions.GetGridCssClass() 8 | } 9 | private static string GetCssClass(this BlockLayout layout) 10 | { 11 | return layout.LayoutType switch 12 | { 13 | BlockLayoutType.Grid => "grid", 14 | BlockLayoutType.Fixed => "fixed", 15 | _ => "grid" 16 | }; 17 | } 18 | 19 | /// 20 | /// Returns the inline CSS style for the block layout 21 | /// 22 | /// 23 | /// 24 | public static string GetGridCssStyle(this GridLayoutOptions? options) 25 | { 26 | if (options == null) 27 | { 28 | return "grid-template-columns: repeat(12, 1fr);"; 29 | } 30 | 31 | return options.TemplateColumns switch 32 | { 33 | 1 => "grid-template-columns: repeat(1, 1fr);", 34 | 2 => "grid-template-columns: repeat(2, 1fr);", 35 | 3 => "grid-template-columns: repeat(3, 1fr);", 36 | 4 => "grid-template-columns: repeat(4, 1fr);", 37 | 5 => "grid-template-columns: repeat(5, 1fr);", 38 | 6 => "grid-template-columns: repeat(6, 1fr);", 39 | 7 => "grid-template-columns: repeat(7, 1fr);", 40 | 8 => "grid-template-columns: repeat(8, 1fr);", 41 | 9 => "grid-template-columns: repeat(9, 1fr);", 42 | 10 => "grid-template-columns: repeat(10, 1fr);", 43 | 11 => "grid-template-columns: repeat(11, 1fr);", 44 | 12 => "grid-template-columns: repeat(12, 1fr);", 45 | _ => "grid-template-columns: repeat(1, 1fr);" 46 | }; 47 | } 48 | 49 | private static string GetGridCssClass(this GridLayoutOptions? options) 50 | { 51 | if (options == null) 52 | { 53 | return "grid-cols-12"; 54 | } 55 | 56 | return options.TemplateColumns switch 57 | { 58 | 1 => "grid-cols-1", 59 | 2 => "grid-cols-2", 60 | 3 => "grid-cols-3", 61 | 4 => "grid-cols-4", 62 | 5 => "grid-cols-5", 63 | 6 => "grid-cols-6", 64 | 7 => "grid-cols-7", 65 | 8 => "grid-cols-8", 66 | 9 => "grid-cols-9", 67 | 10 => "grid-cols-10", 68 | 11 => "grid-cols-11", 69 | 12 => "grid-cols-12", 70 | _ => "grid-cols-12" 71 | }; 72 | } 73 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockLayoutType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public enum BlockLayoutType 4 | { 5 | Auto, 6 | Fixed, 7 | Flex, 8 | Grid 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/BlockTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public static class BlockTypes 4 | { 5 | public const string Block = "Block"; 6 | public const string Root = "Root"; 7 | public const string Section = "Section"; 8 | public const string Container = "Container"; 9 | public const string ContentEditable = "ContentEditable"; 10 | public const string Navigation = "Navigation"; 11 | public const string List = "List"; 12 | public const string Link = "Link"; 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/Border.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public record Border 6 | { 7 | public double Width { get; set; } 8 | public string? Color { get; set; } 9 | public string? Style { get; set; } 10 | public string? Radius { get; set; } 11 | public CssUnit Unit { get; set; } 12 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/ContentBlockData.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Marco.Forms.UI.Client.Models; 3 | 4 | public record ContentBlockData : BlockData 5 | { 6 | public string Tag { get; set; } = "p"; 7 | public string Text { get; set; } = ""; 8 | } 9 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/CssUnitExtensions.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public static class CssUnitExtensions 6 | { 7 | public static string ToCssString(this CssUnit unit) 8 | { 9 | return unit switch 10 | { 11 | CssUnit.Px => "px", 12 | CssUnit.Em => "em", 13 | CssUnit.Rem => "rem", 14 | _ => "px" 15 | }; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/FixedLayoutOptions.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | public record FixedLayoutOptions 6 | { 7 | /// 8 | /// The width of the element. 9 | /// 10 | public double Width { get; set; } 11 | 12 | /// 13 | /// The height of the element. 14 | /// 15 | public double Height { get; set; } 16 | 17 | /// 18 | /// The minimum width of the element. 19 | /// 20 | public double? MinWidth { get; set; } 21 | 22 | /// 23 | /// The minimum height of the element. 24 | /// 25 | public double? MinHeight { get; set; } 26 | 27 | /// 28 | /// The maximum width of the element. 29 | /// 30 | public double? MaxWidth { get; set; } 31 | 32 | /// 33 | /// The maximum height of the element. 34 | /// 35 | public double? MaxHeight { get; set; } 36 | 37 | /// 38 | /// The x position of the element, relative to its parent. 39 | /// 40 | public double? X { get; set; } 41 | 42 | /// 43 | /// The y position of the element, relative to its parent. 44 | /// 45 | public double? Y { get; set; } 46 | 47 | /// 48 | /// The z-index of the element. 49 | /// 50 | public int? ZIndex { get; set; } 51 | 52 | /// 53 | /// The CSS unit for the element's size properties. 54 | /// 55 | public CssUnit WidthUnit { get; set; } 56 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/GridAutoColumns.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | /// 4 | /// Utilities for controlling the size of implicitly-created grid columns. 5 | /// 6 | public enum GridAutoColumns 7 | { 8 | None, 9 | Auto, 10 | Min, 11 | Max, 12 | Fr 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/GridAutoFlow.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | /// 4 | /// Utilities for controlling how elements in a grid are auto-placed. 5 | /// 6 | public enum GridAutoFlow 7 | { 8 | None, 9 | Row, 10 | Column, 11 | Dense, 12 | RowDense, 13 | ColumnDense 14 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/GridAutoRows.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | /// 4 | /// Utilities for controlling the size of implicitly-created grid rows. 5 | /// 6 | public enum GridAutoRows 7 | { 8 | None, 9 | Auto, 10 | Min, 11 | Max, 12 | Fr 13 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/GridLayoutOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record GridLayoutOptions 4 | { 5 | /// 6 | /// Use the grid-cols-* utilities to create grids with n equally sized columns. 7 | /// 8 | public int? TemplateColumns { get; set; } 9 | 10 | /// 11 | /// Use the grid-rows-* utilities to create grids with n equally sized rows. 12 | /// 13 | public int? TemplateRows { get; set; } 14 | 15 | /// 16 | /// Make an element span n rows. 17 | /// 18 | public int? RowSpan { get; set; } 19 | 20 | /// 21 | /// Use the row-start-* and row-end-* utilities to make an element start or end at the nth grid line. These can also be 22 | /// combined with the row-span-* utilities to span a specific number of rows. 23 | /// 24 | /// 25 | /// Note that CSS grid lines start at 1, not 0, so a full-height element in a 3-row grid would start at line 1 and end 26 | /// at line 4. 27 | /// 28 | public int? RowStart { get; set; } 29 | 30 | /// 31 | /// Use the row-start-* and row-end-* utilities to make an element start or end at the nth grid line. These can also be 32 | /// combined with the row-span-* utilities to span a specific number of rows. 33 | /// 34 | /// 35 | /// Note that CSS grid lines start at 1, not 0, so a full-height element in a 3-row grid would start at line 1 and end 36 | /// at line 4. 37 | /// 38 | public int? RowEnd { get; set; } 39 | 40 | /// 41 | /// Make an element span n columns. 42 | /// 43 | public int? ColumnSpan { get; set; } 44 | 45 | /// 46 | /// Use the col-start-* and col-end-* utilities to make an element start or end at the nth grid line. These can also be 47 | /// combined with the col-span-* utilities to span a specific number of columns. 48 | /// 49 | /// 50 | /// Note that CSS grid lines start at 1, not 0, so a full-width element in a 6-column grid would start at line 1 and 51 | /// end at line 7. 52 | /// 53 | public int? ColumnStart { get; set; } 54 | 55 | /// 56 | /// Use the col-start-* and col-end-* utilities to make an element start or end at the nth grid line. These can also be 57 | /// combined with the col-span-* utilities to span a specific number of columns. 58 | /// 59 | /// 60 | /// Note that CSS grid lines start at 1, not 0, so a full-width element in a 6-column grid would start at line 1 and 61 | /// end at line 7. 62 | /// 63 | public int? ColumnEnd { get; set; } 64 | 65 | /// 66 | /// Use the gap-x-* and gap-y-* utilities to change the gap between columns and rows independently. 67 | /// 68 | public int ColumnGap { get; set; } 69 | 70 | /// 71 | /// Use the gap-x-* and gap-y-* utilities to change the gap between columns and rows independently. 72 | /// 73 | public int RowGap { get; set; } 74 | 75 | /// 76 | /// Use the gap-* utilities to change the gap between both rows and columns in grid and flexbox layouts. 77 | /// 78 | public int Gap { get; set; } 79 | 80 | /// 81 | /// Use the grid-flow-* utilities to control how the auto-placement algorithm works for a grid layout. 82 | /// 83 | public GridAutoFlow AutoFlow { get; set; } 84 | 85 | /// 86 | /// Use the auto-cols-* utilities to control the size of implicitly-created grid columns. 87 | /// 88 | public GridAutoColumns AutoColumns { get; set; } 89 | 90 | /// 91 | /// Use the auto-rows-* utilities to control the size of implicitly-created grid rows. 92 | /// 93 | public GridAutoRows AutoRows { get; set; } 94 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/LinkBlockData.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record LinkBlockData : BlockData 4 | { 5 | public string Url { get; set; } = ""; 6 | public string Text { get; set; } = ""; 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/ListBlockData.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record ListBlockData : BlockData 4 | { 5 | public ListType ListType { get; set; } 6 | public List Items { get; set; } = []; 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/ListItem.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record ListItem 4 | { 5 | public string Text { get; set; } = ""; 6 | // public ListBlockData? List { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/ListType.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public enum ListType 4 | { 5 | Unordered, 6 | Ordered 7 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/NavigationBlockData.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record NavigationBlockData : BlockData 4 | { 5 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/RootBlock.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record RootBlock : BlockData 4 | { 5 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/SectionBlockData.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | // [JsonConverter(typeof(BlockDataJsonConverter))] 4 | public record SectionBlockData : BlockData 5 | { 6 | public List ChildBlockIds { get; set; } = []; 7 | } 8 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/ServerClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Text.Json; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Marco.Forms.UI.Client.Models; 6 | 7 | public class ServerClient 8 | { 9 | private readonly HttpClient client; 10 | private readonly ILogger logger; 11 | 12 | private static readonly JsonSerializerOptions JsonOptions = new() 13 | { 14 | PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase 15 | }; 16 | 17 | public ServerClient(HttpClient client, ILogger logger, IOptions siteOptions) 18 | { 19 | this.client = client; 20 | this.logger = logger; 21 | client.BaseAddress = new Uri(siteOptions.Value.ApiEndpoint); 22 | } 23 | 24 | public async Task GetAccountDetailsAsync(CancellationToken ct = default) 25 | { 26 | var response = await client.GetFromJsonAsync("/api/accounts/me", cancellationToken: ct); 27 | return response; 28 | } 29 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/SiteOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Marco.Forms.UI.Client.Models; 2 | 3 | public record SiteOptions 4 | { 5 | /// 6 | /// The endpoint for API operations 7 | /// 8 | public string ApiEndpoint { get; set; } = "https://localhost:7083"; 9 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Models/Uuid.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Marco.Forms.UI.Client.Models; 4 | 5 | 6 | [DebuggerDisplay("{ToString()}")] 7 | public struct Uuid 8 | { 9 | public string Value { get; set; } 10 | 11 | public override string ToString() => Value; 12 | 13 | public static string New() => Ulid.NewUlid().ToString(); 14 | } 15 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Forms; 2 | using Marco.Forms.UI.Client.Models; 3 | using Microsoft.AspNetCore.Components; 4 | using Microsoft.AspNetCore.Components.Web; 5 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 6 | using Microsoft.FluentUI.AspNetCore.Components; 7 | using Pure.Blazor.Components; 8 | using DialogService = Microsoft.FluentUI.AspNetCore.Components.DialogService; 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | // javascript 12 | builder.Services.AddScoped(); 13 | 14 | // services 15 | builder.Services.AddScoped(); 16 | builder.Services.AddScoped(); 17 | builder.Services.AddScoped(); 18 | 19 | // theme 20 | builder.Services.AddCascadingValue(_ => 21 | { 22 | var theme = new DefaultTheme(); 23 | var source = new CascadingValueSource(theme, isFixed: false); 24 | return source; 25 | }); 26 | 27 | builder.Services.TryAddCascadingValue(_ => Theme.Auto); 28 | 29 | builder.Services.AddSingleton(); 30 | builder.Services.AddSingleton(); 31 | builder.Services.AddSingleton(); 32 | // builder.Services.AddScoped(); 33 | // builder.Services.AddScoped(); 34 | builder.Services.AddScoped(); 35 | builder.Services.AddScoped(); 36 | builder.Services.AddHttpClient(); 37 | 38 | await builder.Build().RunAsync(); -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI.Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Marco.Forms.UI.Client 10 | @using Pure.Blazor.Components -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @* *@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @Body 4 | 5 |
    6 | An unhandled error has occurred. 7 | Reload 8 | 🗙 9 |
    -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | #blazor-error-ui { 2 | color-scheme: light only; 3 | background: lightyellow; 4 | bottom: 0; 5 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 6 | box-sizing: border-box; 7 | display: none; 8 | left: 0; 9 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 10 | position: fixed; 11 | width: 100%; 12 | z-index: 1000; 13 | } 14 | 15 | #blazor-error-ui .dismiss { 16 | cursor: pointer; 17 | position: absolute; 18 | right: 0.75rem; 19 | top: 0.5rem; 20 | } 21 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/Pages/Error.razor: -------------------------------------------------------------------------------- 1 | @page "/Error" 2 | @using System.Diagnostics 3 | 4 | Error 5 | 6 |

    Error.

    7 |

    An error occurred while processing your request.

    8 | 9 | @if (ShowRequestId) 10 | { 11 |

    12 | Request ID: @RequestId 13 |

    14 | } 15 | 16 |

    Development Mode

    17 |

    18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

    20 |

    21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

    26 | 27 | @code{ 28 | [CascadingParameter] private HttpContext? HttpContext { get; set; } 29 | 30 | private string? RequestId { get; set; } 31 | private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 32 | 33 | protected override void OnInitialized() => 34 | RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; 35 | 36 | } -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/Routes.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Marco.Forms.UI 10 | @using Marco.Forms.UI.Client 11 | @using Marco.Forms.UI.Components -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base 2 | USER $APP_UID 3 | WORKDIR /app 4 | EXPOSE 8080 5 | EXPOSE 8081 6 | 7 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 8 | ARG BUILD_CONFIGURATION=Release 9 | WORKDIR /src 10 | COPY ["Marco.Forms.UI/Marco.Forms.UI/Marco.Forms.UI.csproj", "Marco.Forms.UI/Marco.Forms.UI/"] 11 | COPY ["Marco.Forms.UI/Marco.Forms.UI.Client/Marco.Forms.UI.Client.csproj", "Marco.Forms.UI/Marco.Forms.UI.Client/"] 12 | RUN dotnet restore "Marco.Forms.UI/Marco.Forms.UI/Marco.Forms.UI.csproj" 13 | COPY . . 14 | WORKDIR "/src/Marco.Forms.UI/Marco.Forms.UI" 15 | RUN dotnet build "./Marco.Forms.UI.csproj" -c $BUILD_CONFIGURATION -o /app/build 16 | 17 | FROM build AS publish 18 | ARG BUILD_CONFIGURATION=Release 19 | RUN dotnet publish "./Marco.Forms.UI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false 20 | 21 | FROM base AS final 22 | WORKDIR /app 23 | COPY --from=publish /app/publish . 24 | ENTRYPOINT ["dotnet", "Marco.Forms.UI.dll"] 25 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Marco.Forms.UI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | Linux 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | .dockerignore 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Program.cs: -------------------------------------------------------------------------------- 1 | using Marco.Forms.UI.Client.Components.Forms; 2 | using Marco.Forms.UI.Client.Models; 3 | using Marco.Forms.UI.Components; 4 | using Microsoft.AspNetCore.Components; 5 | using Microsoft.AspNetCore.Components.Web; 6 | using Microsoft.FluentUI.AspNetCore.Components; 7 | using Microsoft.FluentUI.AspNetCore.Components.Components.Tooltip; 8 | using Pure.Blazor.Components; 9 | using DialogService = Microsoft.FluentUI.AspNetCore.Components.DialogService; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | // Add services to the container. 14 | builder.Services.AddRazorComponents() 15 | .AddInteractiveServerComponents() 16 | .AddInteractiveWebAssemblyComponents(); 17 | 18 | // javascript 19 | builder.Services.AddScoped(); 20 | 21 | // services 22 | builder.Services.AddScoped(); 23 | builder.Services.AddScoped(); 24 | builder.Services.AddScoped(); 25 | builder.Services.AddScoped(); 26 | builder.Services.AddScoped(); 27 | builder.Services.AddScoped(); 28 | 29 | builder.Services.AddCascadingValue(sp => 30 | { 31 | var theme = new DefaultTheme(); 32 | var source = new CascadingValueSource(theme, isFixed: true); 33 | return source; 34 | }); 35 | 36 | builder.Services.TryAddCascadingValue(_ => Theme.Auto); 37 | 38 | 39 | builder.Services.AddSingleton(); 40 | builder.Services.AddSingleton(); 41 | builder.Services.AddSingleton(); 42 | // builder.Services.AddScoped(); 43 | // builder.Services.AddScoped(); 44 | builder.Services.AddScoped(); 45 | builder.Services.AddScoped(); 46 | builder.Services.AddHttpClient(); 47 | var app = builder.Build(); 48 | 49 | // Configure the HTTP request pipeline. 50 | if (app.Environment.IsDevelopment()) 51 | { 52 | app.UseWebAssemblyDebugging(); 53 | } 54 | else 55 | { 56 | app.UseExceptionHandler("/Error", createScopeForErrors: true); 57 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 58 | app.UseHsts(); 59 | } 60 | 61 | app.UseHttpsRedirection(); 62 | 63 | 64 | app.UseAntiforgery(); 65 | 66 | app.MapStaticAssets(); 67 | app.MapRazorComponents() 68 | .AddInteractiveServerRenderMode() 69 | .AddInteractiveWebAssemblyRenderMode() 70 | .AddAdditionalAssemblies(typeof(Marco.Forms.UI.Client._Imports).Assembly); 71 | 72 | app.Run(); -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "http://localhost:5077", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | }, 14 | "https": { 15 | "commandName": "Project", 16 | "dotnetRunMessages": true, 17 | "launchBrowser": true, 18 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 19 | "applicationUrl": "https://localhost:7130;http://localhost:5077", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Black.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BlackItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Bold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLight.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Italic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Light.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-LightItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Medium.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-MediumItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Regular.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Thin.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ThinItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Black.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BlackItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Bold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLight.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Italic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Light.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-LightItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Medium.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-MediumItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Regular.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBold.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Thin.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ThinItalic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable-Italic.woff2 -------------------------------------------------------------------------------- /src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codefroglabs/marco/00c89ce89bf39f8336226c584e87d270476729b6/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable.woff2 -------------------------------------------------------------------------------- /src/Marco.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marco.Forms.UI", "Marco.Forms.UI\Marco.Forms.UI\Marco.Forms.UI.csproj", "{4FECB794-984E-46C6-BC1F-F0D6B3B4D1E2}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Marco.Forms.UI.Client", "Marco.Forms.UI\Marco.Forms.UI.Client\Marco.Forms.UI.Client.csproj", "{271944E1-249C-439B-BE50-CE753475E1AA}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1112EED1-B3C9-4613-BF02-5DE7C47C833A}" 8 | ProjectSection(SolutionItems) = preProject 9 | compose.yaml = compose.yaml 10 | ..\LICENSE.md = ..\LICENSE.md 11 | ..\README.md = ..\README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pure.Blazor.Components", "..\components\src\Pure.Blazor.Components\Pure.Blazor.Components.csproj", "{27EA7511-7D33-412B-8666-0463046E3F9A}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pure.Blazor.Components.Icons", "..\components\src\Pure.Blazor.Components.Icons\Pure.Blazor.Components.Icons.csproj", "{C67810E1-76E2-4EAE-A65C-EDE301E1C55F}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {4FECB794-984E-46C6-BC1F-F0D6B3B4D1E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {4FECB794-984E-46C6-BC1F-F0D6B3B4D1E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {4FECB794-984E-46C6-BC1F-F0D6B3B4D1E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {4FECB794-984E-46C6-BC1F-F0D6B3B4D1E2}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {271944E1-249C-439B-BE50-CE753475E1AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {271944E1-249C-439B-BE50-CE753475E1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {271944E1-249C-439B-BE50-CE753475E1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {271944E1-249C-439B-BE50-CE753475E1AA}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {27EA7511-7D33-412B-8666-0463046E3F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {27EA7511-7D33-412B-8666-0463046E3F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {27EA7511-7D33-412B-8666-0463046E3F9A}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {27EA7511-7D33-412B-8666-0463046E3F9A}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {C67810E1-76E2-4EAE-A65C-EDE301E1C55F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C67810E1-76E2-4EAE-A65C-EDE301E1C55F}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C67810E1-76E2-4EAE-A65C-EDE301E1C55F}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {C67810E1-76E2-4EAE-A65C-EDE301E1C55F}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /src/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | marco.forms.ui: 3 | image: marco.forms.ui 4 | build: 5 | context: . 6 | dockerfile: Marco.Forms.UI/Marco.Forms.UI/Dockerfile 7 | 8 | marco.forms.ui.client: 9 | image: marco.forms.ui.client 10 | build: 11 | context: . 12 | dockerfile: Marco.Forms.UI/Marco.Forms.UI.Client/Dockerfile 13 | 14 | --------------------------------------------------------------------------------