From 86ed6122c9e5744b93b1051c11800051367ce5ad Mon Sep 17 00:00:00 2001 From: Chris Bell Date: Tue, 1 Jul 2025 22:21:04 -0500 Subject: [PATCH] Begin on dataset management screens --- .../CharacterTemplates/Templates.razor | 35 +++ SessionZero/Pages/Library/Datasets.razor | 10 - SessionZero/Pages/Library/Datasets.razor.cs | 8 - .../Pages/Library/Datasets/Datasets.razor | 275 +++++++++++++++++ .../Pages/Library/Datasets/Datasets.razor.cs | 8 + .../Library/{ => Datasets}/Datasets.razor.css | 0 SessionZero/Pages/Library/Library.razor | 42 +-- SessionZero/Pages/Library/Templates.razor | 12 - SessionZero/wwwroot/css/app.css | 285 ++++++++++++++++++ new-szf-docs.md | 20 +- 10 files changed, 617 insertions(+), 78 deletions(-) create mode 100644 SessionZero/Pages/Library/CharacterTemplates/Templates.razor delete mode 100644 SessionZero/Pages/Library/Datasets.razor delete mode 100644 SessionZero/Pages/Library/Datasets.razor.cs create mode 100644 SessionZero/Pages/Library/Datasets/Datasets.razor create mode 100644 SessionZero/Pages/Library/Datasets/Datasets.razor.cs rename SessionZero/Pages/Library/{ => Datasets}/Datasets.razor.css (100%) delete mode 100644 SessionZero/Pages/Library/Templates.razor diff --git a/SessionZero/Pages/Library/CharacterTemplates/Templates.razor b/SessionZero/Pages/Library/CharacterTemplates/Templates.razor new file mode 100644 index 0000000..02bbec8 --- /dev/null +++ b/SessionZero/Pages/Library/CharacterTemplates/Templates.razor @@ -0,0 +1,35 @@ +@page "/library/character-templates" + + + + Back to Library + + +

Character Templates

+ +
+ +
+ +

Create

+

Create a new template

+
+ +
+ +

Manage

+

Manage and edit saved templates

+
+ +
+ +

SessionZeroDB

+

Search online for new templates (NOT FUNCTIONAL)

+
+ + +
+ +@code { + +} \ No newline at end of file diff --git a/SessionZero/Pages/Library/Datasets.razor b/SessionZero/Pages/Library/Datasets.razor deleted file mode 100644 index 0c65ccf..0000000 --- a/SessionZero/Pages/Library/Datasets.razor +++ /dev/null @@ -1,10 +0,0 @@ -@page "/Library/Datasets" - - - - Back to Library - - -

Datasets

- -

@_message

\ No newline at end of file diff --git a/SessionZero/Pages/Library/Datasets.razor.cs b/SessionZero/Pages/Library/Datasets.razor.cs deleted file mode 100644 index 2f753ce..0000000 --- a/SessionZero/Pages/Library/Datasets.razor.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace SessionZero.Pages.Library; - -public partial class Datasets : ComponentBase -{ - private string _message = "Datasets Page Loaded Successfully!"; -} \ No newline at end of file diff --git a/SessionZero/Pages/Library/Datasets/Datasets.razor b/SessionZero/Pages/Library/Datasets/Datasets.razor new file mode 100644 index 0000000..87d053d --- /dev/null +++ b/SessionZero/Pages/Library/Datasets/Datasets.razor @@ -0,0 +1,275 @@ +@page "/Library/Datasets" +@using SessionZero.Data +@using SessionZero.Models +@inject NavigationManager NavigationManager +@inject SessionZero.Services.ISzfStorageService SzfStorageService +@inject SzfParser SzfParser +@inject SzfGenerator SzfGenerator + + + + Back to Library + + +

Datasets

+ +@if (!string.IsNullOrEmpty(_message)) +{ +
+

@_message

+
+} + +@switch (CurrentView) +{ + case DatasetView.Overview: +
+
+ +

Create

+

Create a new dataset

+
+ +
+ +

Manage

+

Manage and edit saved datasets

+
+ +
+ +

SessionZeroDB

+

Search online for new datasets (NOT FUNCTIONAL)

+
+
+ break; + + case DatasetView.CreateDataset: +

Create New Dataset

+
+ + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ break; + + case DatasetView.ManageDatasets: +

Manage Datasets

+ @if (SavedDatasets == null) + { +

Loading datasets...

+ } + else if (!SavedDatasets.Any()) + { +
+

No datasets saved yet. Click "Create" to add one.

+
+ } + else + { + + + + + + + + + + + + @foreach (var dataset in SavedDatasets) + { + + + + + + + + } + +
NameTypeVersionAuthorActions
@dataset.Metadata.Name@dataset.DatasetType@dataset.Metadata.Version@dataset.Metadata.Author +
+ + +
+
+ } +
+ +
+ break; +} + +@code { + private enum DatasetView { Overview, CreateDataset, ManageDatasets } + private DatasetView CurrentView = DatasetView.Overview; + private SessionZero.Data.Dataset NewDataset = new(); + private IEnumerable? SavedDatasets; + private string _message = string.Empty; + + // New property to handle Version binding + private string DatasetVersionString + { + get => NewDataset.Metadata.Version?.ToString() ?? string.Empty; + set + { + if (Version.TryParse(value, out var parsedVersion)) + { + NewDataset.Metadata.Version = parsedVersion; + } + else + { + // Handle invalid version string, e.g., set to null or a default, or show an error + // For simplicity, we'll just leave it as is if parsing fails. + // A better approach would be to use a CustomValidator or more robust input handling. + } + } + } + + protected override async Task OnInitializedAsync() + { + await LoadDatasets(); + } + + private void ShowOverview() + { + CurrentView = DatasetView.Overview; + _message = string.Empty; // Clear any messages + NewDataset = new(); // Reset the new dataset form + StateHasChanged(); + } + + private void ShowCreateDataset() + { + CurrentView = DatasetView.CreateDataset; + _message = string.Empty; + NewDataset = new() // Initialize with default values if needed + { + Metadata = new() + { + Guid = Guid.NewGuid(), + Version = new Version(1, 0, 0) // Ensure Version is initialized + } + }; + StateHasChanged(); + } + + private async Task ShowManageDatasets() + { + CurrentView = DatasetView.ManageDatasets; + _message = string.Empty; + await LoadDatasets(); // Reload datasets when entering management view + StateHasChanged(); + } + + private async Task HandleCreateDataset() + { + try + { + var validationResult = NewDataset.Validate(); + if (!validationResult.IsValid) + { + _message = "Validation errors: " + string.Join("; ", validationResult.Errors); + return; + } + + SzfFile szfFile = new SzfFile + { + Guid = NewDataset.Metadata.Guid.ToString(), + Content = SzfGenerator.Generate(NewDataset), + Type = "dataset", + }; + await SzfStorageService.SaveSzfFileAsync(szfFile); + _message = $"Dataset '{NewDataset.Metadata.Name}' created successfully!"; + await LoadDatasets(); // Refresh the list of datasets + ShowOverview(); // Go back to overview after creation + } + catch (Exception ex) + { + _message = $"Error creating dataset: {ex.Message}"; + } + } + + private async Task LoadDatasets() + { + try + { + var allSzfDatasetFiles = await SzfStorageService.GetSzfFilesByTypeAsync("dataset"); + var allSzfObjects = allSzfDatasetFiles.Select(file => SzfParser.Parse(file.Content)); + + SavedDatasets = allSzfObjects.OfType().ToList(); + } + catch (Exception ex) + { + _message = $"Error loading datasets: {ex.Message}"; + SavedDatasets = new List(); // Ensure it's not null on error + } + StateHasChanged(); + } + + private async Task EditDataset(string guid) + { + _message = $"Attempting to edit dataset with GUID: {guid}"; + var datasetFileToEdit = await SzfStorageService.GetSzfFileByGuidAsync(guid); + var datasetToEdit = SzfParser.Parse(datasetFileToEdit.Content); + if (datasetToEdit is Dataset ds) + { + NewDataset = ds; // Populate the form with the dataset to edit + CurrentView = DatasetView.CreateDataset; // Re-use the create form for editing + _message = $"Editing dataset: {ds.Metadata.Name}"; + } + else + { + _message = "Dataset not found for editing."; + } + StateHasChanged(); + } + + private async Task DeleteDataset(string guid) + { + try + { + await SzfStorageService.DeleteSzfFileAsync(guid); + _message = "Dataset deleted successfully!"; + await LoadDatasets(); // Refresh the list after deletion + } + catch (Exception ex) + { + _message = $"Error deleting dataset: {ex.Message}"; + } + StateHasChanged(); + } +} \ No newline at end of file diff --git a/SessionZero/Pages/Library/Datasets/Datasets.razor.cs b/SessionZero/Pages/Library/Datasets/Datasets.razor.cs new file mode 100644 index 0000000..b6da6ea --- /dev/null +++ b/SessionZero/Pages/Library/Datasets/Datasets.razor.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Components; + +namespace SessionZero.Pages.Library.Datasets; + +public partial class Datasets : ComponentBase +{ + +} \ No newline at end of file diff --git a/SessionZero/Pages/Library/Datasets.razor.css b/SessionZero/Pages/Library/Datasets/Datasets.razor.css similarity index 100% rename from SessionZero/Pages/Library/Datasets.razor.css rename to SessionZero/Pages/Library/Datasets/Datasets.razor.css diff --git a/SessionZero/Pages/Library/Library.razor b/SessionZero/Pages/Library/Library.razor index 2024cc2..98bd909 100644 --- a/SessionZero/Pages/Library/Library.razor +++ b/SessionZero/Pages/Library/Library.razor @@ -1,9 +1,11 @@ @page "/Library" @inject NavigationManager NavigationManager + +

Library

- +

Character Sheet Templates

Create and manage Character Sheet Templates

@@ -13,40 +15,4 @@

Datasets

Create and manage Datasets

-
- - - - -@code { - private void NavigateToTemplates() - { - - } -} \ No newline at end of file + \ No newline at end of file diff --git a/SessionZero/Pages/Library/Templates.razor b/SessionZero/Pages/Library/Templates.razor deleted file mode 100644 index 064269b..0000000 --- a/SessionZero/Pages/Library/Templates.razor +++ /dev/null @@ -1,12 +0,0 @@ -@page "/library/templates" - - - - Back to Library - - -

Templates

- -@code { - -} \ No newline at end of file diff --git a/SessionZero/wwwroot/css/app.css b/SessionZero/wwwroot/css/app.css index 527890f..c4729a1 100644 --- a/SessionZero/wwwroot/css/app.css +++ b/SessionZero/wwwroot/css/app.css @@ -100,6 +100,30 @@ html, body, #app { font-size: 0.9rem; } +.library-card-container { + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + justify-content: flex-start; +} + +.library-card-container .card { + flex: 1; + min-width: 250px; + max-width: calc(50% - 0.75rem); + padding: 1.5rem; +} + +.library-card-container .card p { + color: var(--neutral-medium); +} + +.library-card-container .card h1 { + font-size: 1.6em; + color: var(--heading-color); + font-weight: normal; +} + .button-main { background-color: var(--primary-color); padding: 0.6rem 1.2rem; @@ -145,6 +169,267 @@ html, body, #app { transition: all 0.2s ease; } +/* Forms and Input Elements */ +.form-group { + margin-bottom: 1rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + color: var(--heading-color); + font-weight: 600; + font-size: 0.9rem; +} + +.form-control { + width: 100%; + padding: 0.8rem 1rem; + background-color: var(--form-background); + border: 1px solid var(--primary-color); + border-radius: 4px; + color: var(--text-color); + font-size: 1rem; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.form-control:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(91, 137, 179, 0.3); /* Accent color with transparency */ +} + +.form-control::placeholder { + color: var(--neutral-medium); +} + +textarea.form-control { + min-height: 100px; + resize: vertical; +} + +select.form-control { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url('../res/icons/arrow-left.svg'); /* Using local arrow-left.svg */ + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 1em; + padding-right: 2.5rem; /* Make space for the arrow */ +} + +/* Checkboxes and Radios */ +.form-check { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.form-check-input { + margin-right: 0.75rem; + width: 1.25rem; + height: 1.25rem; + border: 1px solid var(--primary-color-light); + border-radius: 3px; + background-color: var(--form-background); + cursor: pointer; + position: relative; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.form-check-input:checked { + background-color: var(--accent-color); + border-color: var(--accent-color); +} + +.form-check-input[type="checkbox"]:checked::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 6px; + height: 10px; + border: solid var(--text-color); + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.form-check-input[type="radio"] { + border-radius: 50%; +} + +.form-check-input[type="radio"]:checked::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-color); + transform: translate(-50%, -50%); +} + +.form-check-label { + color: var(--text-color); + cursor: pointer; + font-size: 1rem; +} + +/* Tables */ +.data-table { + width: 100%; + border-collapse: collapse; + margin-top: 1.5rem; + background-color: var(--secondary-color); + border-radius: 8px; + overflow: hidden; /* Ensures rounded corners apply to content */ +} + +.data-table th, +.data-table td { + padding: 1rem 1.2rem; + text-align: left; + border-bottom: 1px solid var(--neutral-dark); +} + +.data-table thead { + background-color: var(--primary-color); +} + +.data-table th { + color: var(--heading-color); + font-weight: 600; + font-size: 0.9rem; + text-transform: uppercase; +} + +.data-table tbody tr { + transition: background-color 0.2s ease; +} + +.data-table tbody tr:nth-child(even) { + background-color: var(--secondary-color); +} + +.data-table tbody tr:nth-child(odd) { + background-color: var(--form-background); +} + +.data-table tbody tr:hover { + background-color: var(--primary-color-light); + cursor: pointer; +} + +.data-table td { + color: var(--text-color); + font-size: 0.95rem; +} + +/* Lists (e.g., for items or dynamic content) */ +.custom-list { + list-style: none; + padding: 0; + margin: 1.5rem 0; + background-color: var(--secondary-color); + border-radius: 8px; + overflow: hidden; +} + +.custom-list-item { + padding: 1rem 1.2rem; + border-bottom: 1px solid var(--neutral-dark); + color: var(--text-color); + transition: background-color 0.2s ease; +} + +.custom-list-item:last-child { + border-bottom: none; +} + +.custom-list-item:hover { + background-color: var(--primary-color-light); + cursor: pointer; +} + +.custom-list-item-header { + font-weight: 600; + color: var(--heading-color); + margin-bottom: 0.5rem; +} + +.custom-list-item-meta { + font-size: 0.85rem; + color: var(--neutral-medium); +} + +/* Custom Visual Elements (e.g., panels, containers) */ +.panel { + background-color: var(--secondary-color); + padding: 1.5rem; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + margin-bottom: 1.5rem; +} + +.panel-header { + font-size: 1.5rem; + color: var(--heading-color); + margin-bottom: 1rem; + border-bottom: 1px solid var(--neutral-dark); + padding-bottom: 0.5rem; +} + +.panel-content { + color: var(--text-color); + line-height: 1.6; +} + +.flex-container { + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.flex-item { + flex: 1; + min-width: 250px; +} + +/* Action Buttons within forms/lists */ +.button-action { + background-color: var(--success-color); + padding: 0.5rem 1rem; + border-radius: 4px; + border: none; + cursor: pointer; + font-size: 0.85rem; + color: var(--button-text); + transition: background-color 0.2s ease; +} + +.button-action:hover { + background-color: var(--success-color-hover); +} + +.button-danger { + background-color: var(--danger-color); +} + +.button-danger:hover { + background-color: var(--danger-color-hover); +} + +.button-group { + display: flex; + gap: 0.5rem; + margin-top: 1rem; +} + /* ------------------------------------------------- */ diff --git a/new-szf-docs.md b/new-szf-docs.md index 3efada9..44da1cc 100644 --- a/new-szf-docs.md +++ b/new-szf-docs.md @@ -79,16 +79,16 @@ FieldName (text) = Some Value # This is an inline comment. The .szf format supports a variety of field types to handle different kinds of data. These types are declared in character_template and dataset files. -| Type | Description | Example Value | -| :---- | :---- | :---- | -| text | A single line of text. | Elara the Brave | -| text-field | A multi-line block of text. | A long sword forged by...\nIt glows faintly. | -| number | An integer or floating-point number. | 16 or 3.5 | -| bool | A boolean value. | true or false | -| calculated | A value derived from a formula. | (Strength - 10) / 2 | -| system | A special field that controls application behavior. | items | -| entry-reference | A reference to a single entry from a dataset. | ClassData or ItemData.Longsword | -| entry-reference-list | A comma-separated list of references to entries from a dataset. | CoreFeats.PowerAttack, CoreFeats.Cleave | +| Type | Description | Example Value | +|:---------------------|:----------------------------------------------------------------|:---------------------------------------------| +| text | A single line of text. | Elara the Brave | +| text-field | A multi-line block of text. | A long sword forged by...\nIt glows faintly. | +| number | An integer or floating-point number. | 16 or 3.5 | +| bool | A boolean value. | true or false | +| calculated | A value derived from a formula. | (Strength - 10) / 2 | +| system | A special field that controls application behavior. | | +| entry-reference | A reference to a single entry from a dataset. | ClassData or ItemData.Longsword | +| entry-reference-list | A comma-separated list of references to entries from a dataset. | CoreFeats.PowerAttack, CoreFeats.Cleave | ## **4. File Type Specifications**