├── .gitmodules
├── src
├── Marco.Forms.UI
│ ├── Marco.Forms.UI.Client
│ │ ├── Components
│ │ │ ├── Samples
│ │ │ │ ├── _Imports.razor
│ │ │ │ ├── MyDataCapturePage.razor
│ │ │ │ ├── FormLoader.razor
│ │ │ │ └── EmbedFormGPT.razor
│ │ │ ├── Actions
│ │ │ │ ├── BlockAction.cs
│ │ │ │ ├── MoveAction.cs
│ │ │ │ ├── MoveDownAction.cs
│ │ │ │ ├── MoveUpAction.cs
│ │ │ │ ├── DebugBlockAction.cs
│ │ │ │ ├── DeleteBlockAction.cs
│ │ │ │ ├── BlockActions.cs
│ │ │ │ ├── EditBlockAction.cs
│ │ │ │ ├── ChangeSectionAction.cs
│ │ │ │ └── InsertBlockAction.cs
│ │ │ ├── CssUnit.cs
│ │ │ ├── Forms
│ │ │ │ ├── FormFieldEditor.razor
│ │ │ │ ├── FieldValueType.cs
│ │ │ │ ├── FormSchema.cs
│ │ │ │ ├── ShareLink.cs
│ │ │ │ ├── FormFieldOption.cs
│ │ │ │ ├── FormResponse.cs
│ │ │ │ ├── FormStatus.cs
│ │ │ │ ├── FormSection.cs
│ │ │ │ ├── MoreButton.razor
│ │ │ │ ├── IFormSuggestionService.cs
│ │ │ │ ├── FormSubmission.cs
│ │ │ │ ├── FormField.cs
│ │ │ │ ├── ServerFormAiClient.cs
│ │ │ │ ├── FieldSchema.cs
│ │ │ │ ├── FieldValue.cs
│ │ │ │ ├── FormsClient.cs
│ │ │ │ ├── FormPreviewDialog.razor
│ │ │ │ ├── FormEditor.razor
│ │ │ │ ├── Form.cs
│ │ │ │ ├── FormDto.cs
│ │ │ │ ├── FormCanvas.razor
│ │ │ │ ├── FormRenderer.razor
│ │ │ │ ├── FieldType.cs
│ │ │ │ ├── FormBuilderContainer.razor
│ │ │ │ ├── RadioBlock.cs
│ │ │ │ ├── SuggestionPreview.razor
│ │ │ │ ├── CheckboxBlock.cs
│ │ │ │ ├── FormStore.cs
│ │ │ │ ├── MarcoForm.razor
│ │ │ │ ├── FormFieldRenderer.razor
│ │ │ │ ├── CheckboxField.razor
│ │ │ │ ├── FormResponsePage.razor
│ │ │ │ ├── SelectBlock.cs
│ │ │ │ └── FormCard.razor
│ │ │ ├── Trees
│ │ │ │ ├── TreeItemType.cs
│ │ │ │ └── TreeViewItem.cs
│ │ │ ├── BlockLayoutType.cs
│ │ │ ├── Properties
│ │ │ │ ├── PositionAnchor.cs
│ │ │ │ ├── BoxPosition.cs
│ │ │ │ ├── BoxPositionExtensions.cs
│ │ │ │ ├── SectionProperties.razor
│ │ │ │ └── SpacingAdjuster.razor
│ │ │ ├── NodeType.cs
│ │ │ ├── TextAlign.cs
│ │ │ ├── Shared
│ │ │ │ ├── TopToolbar.razor
│ │ │ │ ├── DefaultAppBar.razor
│ │ │ │ ├── RedirectToLogin.razor
│ │ │ │ ├── Error.razor
│ │ │ │ ├── DefaultErrorBoundary.cs
│ │ │ │ ├── FullPageLayout.razor
│ │ │ │ ├── HoverToggle.razor
│ │ │ │ ├── Overlay.cs
│ │ │ │ ├── ProfileButton.razor
│ │ │ │ ├── Pressable.cs
│ │ │ │ ├── AppBar.razor
│ │ │ │ ├── TrackHover.cs
│ │ │ │ ├── PopupMenu.cs
│ │ │ │ └── FullPageComponent.razor
│ │ │ ├── IPositioner.cs
│ │ │ ├── WizardStepDefinition.cs
│ │ │ ├── GridAutoRows.cs
│ │ │ ├── GridAutoColumns.cs
│ │ │ ├── Primitives
│ │ │ │ ├── FontWeights.cs
│ │ │ │ └── Selection.cs
│ │ │ ├── GridAutoFlow.cs
│ │ │ ├── MouseEventExtensions.cs
│ │ │ ├── SelectionRange.cs
│ │ │ ├── BlockActionMessage.cs
│ │ │ ├── DomRect.cs
│ │ │ ├── Node.cs
│ │ │ ├── BlockLayout.cs
│ │ │ ├── SelectionExtensions.cs
│ │ │ ├── Controls
│ │ │ │ ├── ColorPicker.razor
│ │ │ │ ├── DockableContent.razor
│ │ │ │ ├── FloatingContainerContent.razor
│ │ │ │ └── FloatingContainer.razor
│ │ │ ├── Blocks
│ │ │ │ ├── ContainerBlockView.cs
│ │ │ │ ├── SectionViewBlock.razor.cs
│ │ │ │ ├── BlockDefinitionGroupTitle.razor
│ │ │ │ ├── StatePickerBlock.cs
│ │ │ │ ├── IBlazorBlock.cs
│ │ │ │ ├── BlockComponentBase.cs
│ │ │ │ ├── LinkBlock.cs
│ │ │ │ ├── SectionViewBlock.razor
│ │ │ │ ├── SectionBlock.razor
│ │ │ │ ├── ListBlock.cs
│ │ │ │ ├── SectionBlock.razor.cs
│ │ │ │ ├── BlockEditContainer.razor
│ │ │ │ └── BlockDefinitionList.razor
│ │ │ ├── Spacing.cs
│ │ │ ├── MoveBlockDialog.razor
│ │ │ ├── ScreenPositioner.cs
│ │ │ ├── MarcoCanvas.razor
│ │ │ ├── WizardStep.razor
│ │ │ ├── MarcoCanvas.razor.cs
│ │ │ ├── ExampleWizard.razor
│ │ │ ├── FixedLayoutOptions.cs
│ │ │ ├── MarcoCanvas.razor.js
│ │ │ ├── Layers.razor
│ │ │ ├── PageBlock.cs
│ │ │ ├── BlockController.razor.cs
│ │ │ ├── Wizard.razor
│ │ │ ├── ResizableContainer.razor
│ │ │ ├── BlockLayoutExtensions.cs
│ │ │ └── GridLayoutOptions.cs
│ │ ├── Models
│ │ │ ├── RootBlock.cs
│ │ │ ├── ListType.cs
│ │ │ ├── NavigationBlockData.cs
│ │ │ ├── AccountStatus.cs
│ │ │ ├── BlockFontFamily.cs
│ │ │ ├── BlockLayoutType.cs
│ │ │ ├── ListItem.cs
│ │ │ ├── LinkBlockData.cs
│ │ │ ├── AccountDetails.cs
│ │ │ ├── ListBlockData.cs
│ │ │ ├── ContentBlockData.cs
│ │ │ ├── SectionBlockData.cs
│ │ │ ├── SiteOptions.cs
│ │ │ ├── GridAutoRows.cs
│ │ │ ├── GridAutoColumns.cs
│ │ │ ├── BlockDefinition.cs
│ │ │ ├── GridAutoFlow.cs
│ │ │ ├── Uuid.cs
│ │ │ ├── Border.cs
│ │ │ ├── CssUnitExtensions.cs
│ │ │ ├── BlockLayout.cs
│ │ │ ├── BlockTypes.cs
│ │ │ ├── BlockFormatting.cs
│ │ │ ├── BlockData.cs
│ │ │ ├── ServerClient.cs
│ │ │ ├── FixedLayoutOptions.cs
│ │ │ ├── BlockDefaults.cs
│ │ │ ├── BlockLayoutExtensions.cs
│ │ │ └── GridLayoutOptions.cs
│ │ ├── _Imports.razor
│ │ ├── Dockerfile
│ │ ├── Program.cs
│ │ └── Marco.Forms.UI.Client.csproj
│ └── Marco.Forms.UI
│ │ ├── wwwroot
│ │ └── css
│ │ │ └── inter
│ │ │ ├── Inter-Black.woff2
│ │ │ ├── Inter-Bold.woff2
│ │ │ ├── Inter-Light.woff2
│ │ │ ├── Inter-Thin.woff2
│ │ │ ├── Inter-Italic.woff2
│ │ │ ├── Inter-Medium.woff2
│ │ │ ├── Inter-Regular.woff2
│ │ │ ├── Inter-SemiBold.woff2
│ │ │ ├── InterVariable.woff2
│ │ │ ├── Inter-BoldItalic.woff2
│ │ │ ├── Inter-ExtraBold.woff2
│ │ │ ├── Inter-ExtraLight.woff2
│ │ │ ├── Inter-ThinItalic.woff2
│ │ │ ├── Inter-BlackItalic.woff2
│ │ │ ├── Inter-LightItalic.woff2
│ │ │ ├── Inter-MediumItalic.woff2
│ │ │ ├── InterDisplay-Black.woff2
│ │ │ ├── InterDisplay-Bold.woff2
│ │ │ ├── InterDisplay-Italic.woff2
│ │ │ ├── InterDisplay-Light.woff2
│ │ │ ├── InterDisplay-Medium.woff2
│ │ │ ├── InterDisplay-Thin.woff2
│ │ │ ├── Inter-ExtraBoldItalic.woff2
│ │ │ ├── Inter-SemiBoldItalic.woff2
│ │ │ ├── InterDisplay-Regular.woff2
│ │ │ ├── InterDisplay-SemiBold.woff2
│ │ │ ├── InterVariable-Italic.woff2
│ │ │ ├── Inter-ExtraLightItalic.woff2
│ │ │ ├── InterDisplay-BlackItalic.woff2
│ │ │ ├── InterDisplay-BoldItalic.woff2
│ │ │ ├── InterDisplay-ExtraBold.woff2
│ │ │ ├── InterDisplay-ExtraLight.woff2
│ │ │ ├── InterDisplay-LightItalic.woff2
│ │ │ ├── InterDisplay-ThinItalic.woff2
│ │ │ ├── InterDisplay-MediumItalic.woff2
│ │ │ ├── InterDisplay-ExtraBoldItalic.woff2
│ │ │ ├── InterDisplay-ExtraLightItalic.woff2
│ │ │ └── InterDisplay-SemiBoldItalic.woff2
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ ├── Components
│ │ ├── Layout
│ │ │ ├── MainLayout.razor
│ │ │ └── MainLayout.razor.css
│ │ ├── Routes.razor
│ │ ├── _Imports.razor
│ │ ├── App.razor
│ │ └── Pages
│ │ │ └── Error.razor
│ │ ├── Marco.Forms.UI.csproj
│ │ ├── Dockerfile
│ │ ├── Properties
│ │ └── launchSettings.json
│ │ └── Program.cs
├── compose.yaml
├── .dockerignore
└── Marco.sln
├── README.md
└── LICENSE.md
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "components"]
2 | path = components
3 | url = https://github.com/pureblazor/components.git
4 |
--------------------------------------------------------------------------------
/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/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/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/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/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/wwwroot/css/inter/Inter-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Black.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Bold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Light.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Thin.woff2
--------------------------------------------------------------------------------
/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/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/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/wwwroot/css/inter/Inter-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Italic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Medium.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/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/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/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/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLight.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ThinItalic.woff2
--------------------------------------------------------------------------------
/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/wwwroot/css/inter/Inter-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-BlackItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-LightItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-MediumItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Black.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Bold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/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/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Light.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Medium.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Thin.woff2
--------------------------------------------------------------------------------
/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/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/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/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/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/wwwroot/css/inter/Inter-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/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/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterVariable-Italic.woff2
--------------------------------------------------------------------------------
/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/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/wwwroot/css/inter/Inter-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/Inter-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BlackItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/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/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBold.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLight.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-LightItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ThinItalic.woff2
--------------------------------------------------------------------------------
/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/wwwroot/css/inter/InterDisplay-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-MediumItalic.woff2
--------------------------------------------------------------------------------
/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/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/wwwroot/css/inter/InterDisplay-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefroglabs/marco/HEAD/src/Marco.Forms.UI/Marco.Forms.UI/wwwroot/css/inter/InterDisplay-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/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/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/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.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/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/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/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/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/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/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/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/ShareLink.cs:
--------------------------------------------------------------------------------
1 | namespace Marco.Forms.UI.Client.Components.Forms;
2 |
3 | public record ShareLink
4 | {
5 | public required string FormId { get; set; }
6 | public required string PublicLink { get; set; }
7 | }
--------------------------------------------------------------------------------
/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/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/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/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/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/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.Client/Components/Shared/DefaultAppBar.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 | @code {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Forms/MoreButton.razor:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/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/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/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/Components/Routes.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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/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/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/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/.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/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/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/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/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/_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.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/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/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/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/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/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/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.Client/Components/Forms/ServerFormAiClient.cs:
--------------------------------------------------------------------------------
1 | namespace Marco.Forms.UI.Client.Components.Forms;
2 |
3 | public class ServerFormAiClient
4 | {
5 | public Task ListenForFormRequestAsync(Func onFormGenerated)
6 | {
7 | // implementation removed
8 | return Task.CompletedTask;
9 | }
10 |
11 | public Task GenerateFormAsync(string request)
12 | {
13 | // implementation removed
14 | return Task.CompletedTask;
15 | }
16 | }
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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.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/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/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/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/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/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/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/Blocks/BlockDefinitionGroupTitle.razor:
--------------------------------------------------------------------------------
1 |
2 |
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/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/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/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.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/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.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/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/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/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/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/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/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/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.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/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/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.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/Shared/ProfileButton.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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/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/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/SectionViewBlock.razor:
--------------------------------------------------------------------------------
1 | @using Marco.Forms.UI.Client.Models
2 | @using Marco.Forms.UI.Client.Components.Shared
3 | @inherits BlockComponentBase
4 |
5 |
27 |
--------------------------------------------------------------------------------
/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/Blocks/SectionBlock.razor:
--------------------------------------------------------------------------------
1 | @using Marco.Forms.UI.Client.Models
2 | @using Marco.Forms.UI.Client.Components.Shared
3 | @inherits BlockComponentBase
4 |
5 |
31 |
--------------------------------------------------------------------------------
/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 | Submit
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/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/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.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/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/Forms/FormEditor.razor:
--------------------------------------------------------------------------------
1 |
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/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/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/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/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 |
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/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/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/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/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/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 | Submit
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/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/FormBuilderContainer.razor:
--------------------------------------------------------------------------------
1 | @page "/form-builder"
2 |
3 | @inject IFormSuggestionService AIService
4 |
5 |
6 |
10 |
11 | @* *@
12 | @* @((IsStepByStepMode) ? "Exit Step-by-Step Mode" : "Enter Step-by-Step Mode") *@
13 | @* *@
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/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/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/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/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/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/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 | OnBlockSelected(parentId)">
9 |
12 | @GetBlockLabel(parentId)
13 |
14 | @foreach (var block in blocks)
15 | {
16 | OnBlockSelected(block.BlockId)" class="ml-2 p-1 hover:bg-gray-100 cursor-pointer">@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/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/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/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/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/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/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/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 |
14 | @Steps[i].Title
15 |
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 | Previous
33 | }
34 | @if (CurrentStepIndex < Steps.Count - 1)
35 | {
36 | Next
37 | }
38 | else
39 | {
40 | Finish
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/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.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/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/Components/Forms/SuggestionPreview.razor:
--------------------------------------------------------------------------------
1 | @using System.Text.Json
2 | @using System.Text.Json.Serialization
3 | @using Marco.Forms.UI.Client.Models
4 | @if (Field != null || FieldJson != null)
5 | {
6 |
7 | @if (Field != null)
8 | {
9 |
10 |
11 |
12 | }
13 | else if (FieldJson != null)
14 | {
15 |
22 | }
23 |
Accept
25 |
26 |
Reject
28 |
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/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.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/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/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/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/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 | Submit
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 |
--------------------------------------------------------------------------------
/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/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/Blocks/BlockDefinitionList.razor:
--------------------------------------------------------------------------------
1 | @using System.Collections.Frozen
2 | @using Marco.Forms.UI.Client.Models
3 | @using Pure.Blazor.Components
4 |
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/Controls/FloatingContainer.razor:
--------------------------------------------------------------------------------
1 | @using Marco.Forms.UI.Client.Components.Properties
2 | @using Pure.Blazor.Components.Icons
3 |
7 |
8 |
9 | @if (WindowPositioner is not null)
10 | {
11 | Close
12 |
13 | }
14 | else
15 | {
16 | @Icon
17 | }
18 |
19 |
20 |
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/Forms/CheckboxField.razor:
--------------------------------------------------------------------------------
1 |
2 | @*
@Field.Name *@
3 |
4 | @foreach (var option in Field.Options)
5 | {
6 | var o = option;
7 |
8 |
Toggle(o))">
16 | @if (option.Value.BooleanValue == true)
17 | {
18 |
25 | }
26 |
27 | @*
Toggle(option)"> *@
32 | @*
*@
37 |
38 |
@option.Label
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/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 | BackgroundColorChanged(TailwindColor.Black)" class="w-8 h-8 cursor-pointer bg-black border-gray-300 border-1 rounded">
9 | BackgroundColorChanged(TailwindColor.White)" class="w-8 h-8 cursor-pointer bg-white border-gray-300 border-1 rounded">
10 | backgroundColorPickerVisible = !backgroundColorPickerVisible)" class="w-8 h-8 border-gray-300 border-1 rounded cursor-pointer bg-gradient-to-br from-red-600 to-blue-600">
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 | { await BackgroundColorChanged(color); backgroundColorPickerVisible = false; })" @onclick="@(() => BackgroundColorChanged(color))" class="w-6 h-6 hover:scale-150 hover:shadow hover:rounded cursor-pointer @($"{TailwindBackgroundColorMapper.MapEnumToTailwindBackgroundColorClass(color)}")">
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/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/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/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 | @*
*@
41 | @* Undock *@
42 | @* *@
43 | @* *@
44 | @* *@
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/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 |
15 |
16 |
17 |
18 |
19 | @selectedForm?.Name
20 |
21 | @if (selectedForm != null && selectedFormResponses.Any())
22 | {
23 |
24 |
25 |
26 | Field Name
27 | Response
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 | @field?.Name
36 | @response.Value
37 |
38 | }
39 |
40 |
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/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(4);
68 | builder.AddComponentParameter(5, nameof(Button.Size), Sizes.Sm);
69 | builder.AddComponentParameter(6, nameof(Button.Variant), Variants.Primary);
70 | builder.AddAttribute(7, "onclick", EventCallback.Factory.Create(this, ItemAdded));
71 | builder.AddAttribute(8, "type", "button");
72 | builder.AddContent(9, "Add");
73 | builder.CloseComponent();
74 | builder.CloseElement();
75 |
76 | builder.CloseRegion();
77 |
78 | builder.CloseElement();
79 | builder.CloseElement();
80 | };
81 | }
82 |
83 | private void ItemAdded(MouseEventArgs e)
84 | {
85 | }
86 |
87 | private void OnChange(ChangeEventArgs e)
88 | {
89 | if (!FieldChanged.HasDelegate)
90 | {
91 | Field.Value.BooleanValue = e.Value?.ToString() == bool.TrueString;
92 | }
93 | else
94 | {
95 | FieldChanged.InvokeAsync(e.Value?.ToString());
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Marco.Forms.UI/Marco.Forms.UI.Client/Components/Properties/SpacingAdjuster.razor:
--------------------------------------------------------------------------------
1 | Spacing (rem)
2 |
3 |
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/Forms/FormCard.razor:
--------------------------------------------------------------------------------
1 | @using Microsoft.FluentUI.AspNetCore.Components
2 |
58 |
59 | @code {
60 |
61 | [Inject]
62 | public required NavigationManager Nav { get; set; }
63 |
64 | [Parameter] public EventCallback OnClick { get; set; }
65 | [Parameter] public required FormDto form { get; set; }
66 | public bool Open { get; set; }
67 |
68 | private void PreviewForm()
69 | {
70 | Nav.NavigateTo($"/forms/{form.FormId}/preview");
71 | }
72 |
73 | private void EditForm()
74 | {
75 | Nav.NavigateTo($"/forms/{form.FormId}/canvas");
76 | }
77 |
78 | private void ShowResponses()
79 | {
80 | Nav.NavigateTo($"/forms/{form.FormId}/responses");
81 | }
82 | }
83 |
--------------------------------------------------------------------------------