diff --git a/Cratis.Templates.csproj b/Cratis.Templates.csproj index 99cea73..31c72e2 100644 --- a/Cratis.Templates.csproj +++ b/Cratis.Templates.csproj @@ -13,6 +13,7 @@ MIT true $(NoWarn);CS8805;CS0103;IDE0073;SA1512;SA1515;SA1005 + false diff --git a/Templates/Cratis/.template.config/template.json b/Templates/Cratis/.template.config/template.json index a743309..73ac641 100644 --- a/Templates/Cratis/.template.config/template.json +++ b/Templates/Cratis/.template.config/template.json @@ -136,52 +136,7 @@ }, { "condition": "(EnableFrontend)", - "description": "Add frontend runtime dependencies.", - "manualInstructions": [ - { - "text": "Run 'yarn add react react-dom'" - } - ], - "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", - "args": { - "executable": "yarn", - "args": "add react react-dom" - }, - "continueOnError": true - }, - { - "condition": "(EnableFrontend)", - "description": "Add frontend development dependencies.", - "manualInstructions": [ - { - "text": "Run 'yarn add --dev @types/react @types/react-dom @vitejs/plugin-react typescript vite'" - } - ], - "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", - "args": { - "executable": "yarn", - "args": "add --dev @types/react @types/react-dom @vitejs/plugin-react typescript vite" - }, - "continueOnError": true - }, - { - "condition": "(EnableFrontend)", - "description": "Add latest Cratis packages.", - "manualInstructions": [ - { - "text": "Run 'yarn add @cratis/arc @cratis/arc.react'" - } - ], - "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", - "args": { - "executable": "yarn", - "args": "add @cratis/arc @cratis/arc.react" - }, - "continueOnError": true - }, - { - "condition": "(EnableFrontend)", - "description": "Install frontend dependencies with yarn.", + "description": "Install all frontend dependencies with yarn.", "manualInstructions": [ { "text": "Run 'yarn install'" diff --git a/Templates/Cratis/App.css b/Templates/Cratis/App.css index 5538820..592d97d 100644 --- a/Templates/Cratis/App.css +++ b/Templates/Cratis/App.css @@ -2,13 +2,4 @@ max-width: 1280px; margin: 0 auto; padding: 2rem; - text-align: center; -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; } diff --git a/Templates/Cratis/App.tsx b/Templates/Cratis/App.tsx index d1e4b94..f97ecea 100644 --- a/Templates/Cratis/App.tsx +++ b/Templates/Cratis/App.tsx @@ -1,25 +1,18 @@ -import { useState } from 'react' -import './App.css' +import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import { DialogComponents } from '@cratis/arc.react/dialogs'; +import { BusyIndicatorDialog, ConfirmationDialog } from '@cratis/components/Dialogs'; +import { SomeFeature } from './Features/SomeFeature'; function App() { - const [count, setCount] = useState(0) - - return ( - <> -

CratisApp

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Built with Cratis Arc and Chronicle -

- - ) + return ( + + + + } /> + + + + ); } -export default App +export default App; diff --git a/Templates/Cratis/Bindings.ts b/Templates/Cratis/Bindings.ts new file mode 100644 index 0000000..cde8e24 --- /dev/null +++ b/Templates/Cratis/Bindings.ts @@ -0,0 +1,7 @@ +import { Bindings as ApplicationModelBindings } from '@cratis/arc.react.mvvm'; + +export class Bindings { + static initialize() { + ApplicationModelBindings.initialize(); + } +} diff --git a/Templates/Cratis/CratisApp.csproj b/Templates/Cratis/CratisApp.csproj index e723aae..9447aa9 100644 --- a/Templates/Cratis/CratisApp.csproj +++ b/Templates/Cratis/CratisApp.csproj @@ -10,4 +10,9 @@ true true + + + + + diff --git a/Templates/Cratis/Features/SomeFeature/Listing/AllListings.ts b/Templates/Cratis/Features/SomeFeature/Listing/AllListings.ts new file mode 100644 index 0000000..e13b1e9 --- /dev/null +++ b/Templates/Cratis/Features/SomeFeature/Listing/AllListings.ts @@ -0,0 +1,47 @@ +// @generated - This file is automatically generated by the Cratis proxy generator. Do not edit manually. + +/* eslint-disable sort-imports */ +import { ObservableQueryFor, QueryResultWithState, Sorting, Paging } from '@cratis/arc/queries'; +import { useObservableQuery, useObservableQueryWithPaging, SetSorting, SetPage, SetPageSize } from '@cratis/arc.react/queries'; +import { ParameterDescriptor } from '@cratis/arc/reflection'; +import { Listing } from './Listing'; + +class AllListingsSortBy { + constructor(readonly query: AllListings) {} +} + +class AllListingsSortByWithoutQuery {} + +export class AllListings extends ObservableQueryFor { + readonly route: string = '/api/some-feature/listing/all-listings'; + readonly defaultValue: Listing[] = []; + private readonly _sortBy: AllListingsSortBy; + private static readonly _sortBy: AllListingsSortByWithoutQuery = new AllListingsSortByWithoutQuery(); + + constructor() { + super(Listing, true); + this._sortBy = new AllListingsSortBy(this); + } + + get requiredRequestParameters(): string[] { + return []; + } + + readonly parameterDescriptors: ParameterDescriptor[] = []; + + get sortBy(): AllListingsSortBy { + return this._sortBy; + } + + static get sortBy(): AllListingsSortByWithoutQuery { + return this._sortBy; + } + + static use(sorting?: Sorting): [QueryResultWithState, SetSorting] { + return useObservableQuery(AllListings, undefined, sorting); + } + + static useWithPaging(pageSize: number, sorting?: Sorting): [QueryResultWithState, SetSorting, SetPage, SetPageSize] { + return useObservableQueryWithPaging(AllListings, new Paging(0, pageSize), undefined, sorting); + } +} diff --git a/Templates/Cratis/Features/SomeFeature/Listing/Listing.cs b/Templates/Cratis/Features/SomeFeature/Listing/Listing.cs index d4e328d..0b78c67 100644 --- a/Templates/Cratis/Features/SomeFeature/Listing/Listing.cs +++ b/Templates/Cratis/Features/SomeFeature/Listing/Listing.cs @@ -1,16 +1,18 @@ -using AnApp.Features.SomeFeature.Registration; +using CratisApp.Features.SomeFeature.Registration; +using MongoDB.Driver; -namespace AnApp.Features.SomeFeature.Listing; +namespace CratisApp.Features.SomeFeature.Listing; -[ReadModel, FromEvent] -public record Listing(string Name, [SetFromContext]EventSourceId EventSourceId) +[ReadModel] +public record Listing(SomeName Name, EventSourceId EventSourceId) { - public static Task GetByEventSourceId(EventSourceId eventSourceId, IReadModels readModels) - => readModels.GetInstanceById(eventSourceId); + public static ISubject> AllListings(IMongoCollection collection) => + collection.Observe(); +} - // public static ISubject ObserveAll(IReadModels readModels) - // => ClientObservable< readModels.Watch(); - - public static Task> GetAll(IReadModels readModels) - => readModels.GetInstances(); -} \ No newline at end of file +public class ListingProjection : IProjectionFor+{ + public void Define(IProjectionBuilderFor builder) => builder + .AutoMap() + .From(); +} diff --git a/Templates/Cratis/Features/SomeFeature/Listing/Listing.ts b/Templates/Cratis/Features/SomeFeature/Listing/Listing.ts new file mode 100644 index 0000000..d89a7de --- /dev/null +++ b/Templates/Cratis/Features/SomeFeature/Listing/Listing.ts @@ -0,0 +1,12 @@ +// @generated - This file is automatically generated by the Cratis proxy generator. Do not edit manually. + +/* eslint-disable sort-imports */ +import { field } from '@cratis/fundamentals'; + +export class Listing { + @field(String) + name!: string; + + @field(String) + eventSourceId!: string; +} diff --git a/Templates/Cratis/Features/SomeFeature/Registration/Register.ts b/Templates/Cratis/Features/SomeFeature/Registration/Register.ts new file mode 100644 index 0000000..55af022 --- /dev/null +++ b/Templates/Cratis/Features/SomeFeature/Registration/Register.ts @@ -0,0 +1,51 @@ +// @generated - This file is automatically generated by the Cratis proxy generator. Do not edit manually. + +/* eslint-disable sort-imports */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +import { Command, CommandValidator } from '@cratis/arc/commands'; +import { useCommand, SetCommandValues, ClearCommandValues } from '@cratis/arc.react/commands'; +import { PropertyDescriptor } from '@cratis/arc/reflection'; +import { Guid } from '@cratis/fundamentals'; + +export interface IRegister { + name?: string; +} + +export class RegisterValidator extends CommandValidator { + constructor() { + super(); + } +} + +export class Register extends Command implements IRegister { + readonly route: string = '/api/some-feature/registration'; + readonly validation: CommandValidator = new RegisterValidator(); + readonly propertyDescriptors: PropertyDescriptor[] = [ + new PropertyDescriptor('name', String, false), + ]; + + private _name!: string; + + constructor() { + super(Guid, false); + } + + get requestParameters(): string[] { + return []; + } + + get name(): string { + return this._name; + } + + set name(value: string) { + this._name = value; + this.propertyChanged('name'); + } + + static use(initialValues?: IRegister): [Register, SetCommandValues, ClearCommandValues] { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return useCommand(Register, initialValues); + } +} diff --git a/Templates/Cratis/Features/SomeFeature/SomeFeature.tsx b/Templates/Cratis/Features/SomeFeature/SomeFeature.tsx new file mode 100644 index 0000000..d613827 --- /dev/null +++ b/Templates/Cratis/Features/SomeFeature/SomeFeature.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; +import { Column } from 'primereact/column'; +import { Button } from 'primereact/button'; +import { DataTableForObservableQuery } from '@cratis/components/DataTables'; +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { InputTextField } from '@cratis/components/CommandForm'; +import { AllListings } from 'Api/SomeFeature/Listing/AllListings'; +import { Register } from 'Api/SomeFeature/Registration/Register'; + +export const SomeFeature = () => { + const [showRegister, setShowRegister] = useState(false); + + return ( +
+
+

SomeFeature

+
+ + + + + { if (result.isSuccess) setShowRegister(false); }} + onCancel={() => setShowRegister(false)}> + + value={c => c.name} + title='Name' + icon={} /> + +
+ ); +}; diff --git a/Templates/Cratis/Features/SomeFeature/SomeName.cs b/Templates/Cratis/Features/SomeFeature/SomeName.cs index e4d65cf..7778d80 100644 --- a/Templates/Cratis/Features/SomeFeature/SomeName.cs +++ b/Templates/Cratis/Features/SomeFeature/SomeName.cs @@ -1,3 +1,3 @@ -namespace AnApp.Features.SomeFeature; +namespace CratisApp.Features.SomeFeature; public record SomeName(string Value) : ConceptAs(Value); \ No newline at end of file diff --git a/Templates/Cratis/Features/SomeFeature/index.ts b/Templates/Cratis/Features/SomeFeature/index.ts new file mode 100644 index 0000000..8c09568 --- /dev/null +++ b/Templates/Cratis/Features/SomeFeature/index.ts @@ -0,0 +1 @@ +export * from './SomeFeature'; diff --git a/Templates/Cratis/GlobalUsings.cs b/Templates/Cratis/GlobalUsings.cs new file mode 100644 index 0000000..09b256e --- /dev/null +++ b/Templates/Cratis/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using System.Reactive.Subjects; +global using Cratis.Arc.Commands; +global using Cratis.Arc.Commands.ModelBound; +global using Cratis.Arc.Queries; +global using Cratis.Arc.Queries.ModelBound; +global using Cratis.Chronicle.Events; +global using Cratis.Chronicle.Keys; +global using Cratis.Chronicle.Projections; +global using Cratis.Chronicle.ReadModels; +global using Cratis.Concepts; +global using MongoDB.Driver; diff --git a/Templates/Cratis/Program.cs b/Templates/Cratis/Program.cs index eff52ad..6ff38d6 100644 --- a/Templates/Cratis/Program.cs +++ b/Templates/Cratis/Program.cs @@ -1,3 +1,4 @@ +using CratisApp.Features.SomeFeature.Registration; using Cratis.Arc.MongoDB; var builder = WebApplication.CreateBuilder(args); diff --git a/Templates/Cratis/index.css b/Templates/Cratis/index.css index 6119ad9..a029587 100644 --- a/Templates/Cratis/index.css +++ b/Templates/Cratis/index.css @@ -1,11 +1,13 @@ +@import "tailwindcss"; +@import 'primereact/resources/themes/lara-dark-blue/theme.css'; + :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 14px; line-height: 1.5; font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: dark; font-synthesis: none; text-rendering: optimizeLegibility; @@ -13,56 +15,14 @@ -moz-osx-font-smoothing: grayscale; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - body { margin: 0; - display: flex; - place-items: center; min-width: 320px; min-height: 100vh; + background-color: var(--surface-ground); + color: var(--text-color); } -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +#root { + height: 100vh; } diff --git a/Templates/Cratis/main.tsx b/Templates/Cratis/main.tsx index bef5202..48aca59 100644 --- a/Templates/Cratis/main.tsx +++ b/Templates/Cratis/main.tsx @@ -1,10 +1,23 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import 'reflect-metadata'; +import { PrimeReactProvider } from 'primereact/api'; +import ReactDOM from 'react-dom/client'; +import 'primeicons/primeicons.css'; +import './index.css'; +import React from 'react'; +import App from './App.tsx'; +import { configure as configureMobx } from 'mobx'; +import { Bindings } from './Bindings'; -createRoot(document.getElementById('root')!).render( - - - , -) +Bindings.initialize(); + +configureMobx({ + enforceActions: 'never' +}); + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/Templates/Cratis/package.json b/Templates/Cratis/package.json index 1b5adb7..7be192b 100644 --- a/Templates/Cratis/package.json +++ b/Templates/Cratis/package.json @@ -5,7 +5,35 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc -b && vite build", "preview": "vite preview" + }, + "dependencies": { + "@cratis/arc": "^19.6.9", + "@cratis/arc.react": "^19.6.9", + "@cratis/arc.react.mvvm": "^19.6.9", + "@cratis/arc.vite": "^19.6.9", + "@cratis/components": "^1.1.4", + "mobx": "^6.15.0", + "mobx-react": "^9.2.1", + "primeicons": "^7.0.0", + "primereact": "^10.9.7", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.0.0", + "reflect-metadata": "^0.2.0", + "ts-deepmerge": "^7.0.0", + "tsyringe": "^4.10.0", + "usehooks-ts": "^3.1.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.5.0", + "vite": "^6.0.0" } } diff --git a/Templates/Cratis/tsconfig.json b/Templates/Cratis/tsconfig.json index 8166e30..24976ab 100644 --- a/Templates/Cratis/tsconfig.json +++ b/Templates/Cratis/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "useDefineForClassFields": true, + "useDefineForClassFields": false, "lib": [ "ES2020", "DOM", @@ -16,14 +16,25 @@ "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", + /* Decorators */ + "experimentalDecorators": true, + "emitDecoratorMetadata": true, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + /* Path aliases */ + "baseUrl": ".", + "paths": { + "Api/*": ["./Features/*"] + } }, "include": [ - "*.ts", - "*.tsx" + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "vite.config.ts" ] -} \ No newline at end of file +} diff --git a/Templates/Cratis/vite.config.ts b/Templates/Cratis/vite.config.ts index 13ea3ae..9f8a3a2 100644 --- a/Templates/Cratis/vite.config.ts +++ b/Templates/Cratis/vite.config.ts @@ -1,19 +1,45 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import { EmitMetadataPlugin } from '@cratis/arc.vite' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + optimizeDeps: { + exclude: ['tslib'], + }, + esbuild: { + supported: { + 'top-level-await': true, + }, + }, + plugins: [ + react(), + tailwindcss(), + EmitMetadataPlugin() as any + ], build: { outDir: '../wwwroot', + assetsDir: '', + modulePreload: false, + target: 'esnext', + minify: false, + cssCodeSplit: false, emptyOutDir: true }, server: { proxy: { '/api': { target: 'http://localhost:5000', - changeOrigin: true + changeOrigin: true, + ws: true } } + }, + resolve: { + alias: { + 'Api': path.resolve('./Features') + } } })