| | | 1 | | @page "/world/{WorldId:guid}" |
| | | 2 | | @attribute [Authorize] |
| | | 3 | | @implements IDisposable |
| | | 4 | | @using Chronicis.Client.Components.Dialogs |
| | | 5 | | @using Chronicis.Client.Components.Shared |
| | | 6 | | @using Chronicis.Client.Components.World |
| | | 7 | | @using Chronicis.Client.Components.Settings |
| | | 8 | | @inject WorldDetailViewModel ViewModel |
| | | 9 | | @inject WorldLinksViewModel LinksViewModel |
| | | 10 | | @inject WorldDocumentsViewModel DocumentsViewModel |
| | | 11 | | @inject WorldSharingViewModel SharingViewModel |
| | | 12 | | @inject IAppContextService AppContext |
| | | 13 | | @inject IJSRuntime JSRuntime |
| | | 14 | | @inject NavigationManager Navigation |
| | | 15 | | |
| | 3 | 16 | | @if (ViewModel.IsLoading) |
| | | 17 | | { |
| | | 18 | | <LoadingSkeleton /> |
| | | 19 | | } |
| | 2 | 20 | | else if (ViewModel.World != null) |
| | | 21 | | { |
| | | 22 | | <MudPaper Elevation="2" Class="chronicis-article-card chronicis-fade-in"> |
| | | 23 | | <DetailPageHeader Breadcrumbs="ViewModel.Breadcrumbs" |
| | | 24 | | Icon="@Icons.Material.Filled.Public" |
| | | 25 | | Title="@ViewModel.EditName" |
| | | 26 | | TitleChanged="@(v => ViewModel.EditName = v)" |
| | | 27 | | Placeholder="World Name" |
| | | 28 | | OnTitleEdited="@(() => {})" |
| | | 29 | | OnEnterPressed="@(() => ViewModel.SaveAsync(SharingViewModel))" |
| | | 30 | | ReadOnly="@(!ViewModel.CanManageWorldDetails)" /> |
| | | 31 | | |
| | | 32 | | <!-- Description --> |
| | | 33 | | <MudTextField Value="@ViewModel.EditDescription" |
| | | 34 | | ValueChanged="@((string v) => ViewModel.EditDescription = v)" |
| | | 35 | | Label="Description" |
| | | 36 | | Variant="Variant.Outlined" |
| | | 37 | | Lines="5" |
| | | 38 | | AutoGrow="true" |
| | | 39 | | MaxLines="20" |
| | | 40 | | Placeholder="Describe your world..." |
| | | 41 | | Class="mb-4" |
| | | 42 | | Immediate="true" |
| | | 43 | | ReadOnly="@(!ViewModel.CanManageWorldDetails)" /> |
| | | 44 | | |
| | | 45 | | <!-- Overview Section: Stats + Quick Actions --> |
| | | 46 | | <div class="world-overview mb-4"> |
| | | 47 | | <div class="world-stats-grid"> |
| | | 48 | | <div class="stat-card"> |
| | | 49 | | <MudIcon Icon="@Icons.Material.Filled.AutoStories" Class="stat-icon" /> |
| | | 50 | | <div class="stat-content"> |
| | | 51 | | <div class="stat-value">@ViewModel.World.CampaignCount</div> |
| | | 52 | | <div class="stat-label">Campaigns</div> |
| | | 53 | | </div> |
| | | 54 | | </div> |
| | | 55 | | <div class="stat-card"> |
| | | 56 | | <MudIcon Icon="@Icons.Material.Filled.Group" Class="stat-icon" /> |
| | | 57 | | <div class="stat-content"> |
| | | 58 | | <div class="stat-value">@ViewModel.World.MemberCount</div> |
| | | 59 | | <div class="stat-label">Members</div> |
| | | 60 | | </div> |
| | | 61 | | </div> |
| | | 62 | | <div class="stat-card"> |
| | | 63 | | <MudIcon Icon="@Icons.Material.Filled.CalendarToday" Class="stat-icon" /> |
| | | 64 | | <div class="stat-content"> |
| | | 65 | | <div class="stat-value">@ViewModel.World.CreatedAt.ToString("MMM yyyy")</div> |
| | | 66 | | <div class="stat-label">Created</div> |
| | | 67 | | </div> |
| | | 68 | | </div> |
| | | 69 | | </div> |
| | | 70 | | |
| | | 71 | | <div class="quick-actions-compact"> |
| | | 72 | | @if (ViewModel.CanManageWorldDetails) |
| | | 73 | | { |
| | | 74 | | <MudButton Variant="Variant.Filled" |
| | | 75 | | Color="Color.Primary" |
| | | 76 | | StartIcon="@Icons.Material.Filled.AutoStories" |
| | | 77 | | OnClick="@(() => ViewModel.CreateCampaignAsync())" |
| | | 78 | | Size="Size.Small"> |
| | | 79 | | New Campaign |
| | | 80 | | </MudButton> |
| | | 81 | | } |
| | | 82 | | <MudButton Variant="Variant.Filled" |
| | | 83 | | Color="Color.Primary" |
| | | 84 | | StartIcon="@Icons.Material.Filled.Person" |
| | | 85 | | OnClick="@(() => ViewModel.CreateCharacterAsync())" |
| | | 86 | | Size="Size.Small"> |
| | | 87 | | New Character |
| | | 88 | | </MudButton> |
| | | 89 | | <MudButton Variant="Variant.Filled" |
| | | 90 | | Color="Color.Primary" |
| | | 91 | | StartIcon="@Icons.Material.Filled.MenuBook" |
| | | 92 | | OnClick="@(() => ViewModel.CreateWikiArticleAsync())" |
| | | 93 | | Size="Size.Small"> |
| | | 94 | | New Article |
| | | 95 | | </MudButton> |
| | | 96 | | </div> |
| | | 97 | | </div> |
| | | 98 | | |
| | | 99 | | <!-- Tabbed Content --> |
| | | 100 | | <MudTabs Elevation="0" |
| | | 101 | | Rounded="true" |
| | | 102 | | ApplyEffectsToContainer="true" |
| | | 103 | | PanelClass="pa-4"> |
| | | 104 | | |
| | | 105 | | <!-- Resources Tab --> |
| | | 106 | | <MudTabPanel Text="Resources" Icon="@Icons.Material.Filled.Folder"> |
| | | 107 | | <div class="resources-section"> |
| | | 108 | | <!-- Links Subsection --> |
| | | 109 | | <div class="resource-subsection mb-4"> |
| | | 110 | | <div class="d-flex align-center justify-space-between mb-2"> |
| | | 111 | | <MudText Typo="Typo.subtitle1" Style="font-weight: 600;">External Links</MudText> |
| | | 112 | | @if (ViewModel.CanManageWorldDetails) |
| | | 113 | | { |
| | | 114 | | <MudButton Variant="Variant.Text" |
| | | 115 | | Color="Color.Primary" |
| | | 116 | | StartIcon="@Icons.Material.Filled.Add" |
| | | 117 | | OnClick="@(() => LinksViewModel.StartAddLink())" |
| | | 118 | | Size="Size.Small"> |
| | | 119 | | Add Link |
| | | 120 | | </MudButton> |
| | | 121 | | } |
| | | 122 | | </div> |
| | | 123 | | |
| | | 124 | | @if (LinksViewModel.Links.Count == 0 && !LinksViewModel.IsAddingLink) |
| | | 125 | | { |
| | | 126 | | <MudText Typo="Typo.body2" Class="mud-text-secondary"> |
| | | 127 | | No external links yet. Add links to Roll20, D&D Beyond, or other resources. |
| | | 128 | | </MudText> |
| | | 129 | | } |
| | | 130 | | |
| | | 131 | | @foreach (var link in LinksViewModel.Links) |
| | | 132 | | { |
| | | 133 | | <div class="resource-item"> |
| | | 134 | | @if (LinksViewModel.EditingLinkId == link.Id) |
| | | 135 | | { |
| | | 136 | | <MudIcon Icon="@Icons.Material.Filled.Link" Size="Size.Small" Style="color: var(--ch |
| | | 137 | | <MudTextField Value="@LinksViewModel.EditLinkTitle" |
| | | 138 | | ValueChanged="@((string v) => LinksViewModel.EditLinkTitle = v)" |
| | | 139 | | Placeholder="Title" Variant="Variant.Outlined" Margin="Margin.Dense" S |
| | | 140 | | <MudTextField Value="@LinksViewModel.EditLinkUrl" |
| | | 141 | | ValueChanged="@((string v) => LinksViewModel.EditLinkUrl = v)" |
| | | 142 | | Placeholder="URL" Variant="Variant.Outlined" Margin="Margin.Dense" Cla |
| | | 143 | | <MudTextField Value="@LinksViewModel.EditLinkDescription" |
| | | 144 | | ValueChanged="@((string v) => LinksViewModel.EditLinkDescription = v)" |
| | | 145 | | Placeholder="Description (optional)" Variant="Variant.Outlined" Margin |
| | | 146 | | <MudIconButton Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size. |
| | | 147 | | OnClick="@(() => LinksViewModel.SaveEditLinkAsync())" Disabled="Links |
| | | 148 | | <MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" Size="Size. |
| | | 149 | | OnClick="@(() => LinksViewModel.CancelEditLink())" /> |
| | | 150 | | } |
| | | 151 | | else |
| | | 152 | | { |
| | | 153 | | <img src="@WorldLinksViewModel.GetFaviconUrl(link.Url)" alt="" style="width: 20px; h |
| | | 154 | | <div class="flex-grow-1"> |
| | | 155 | | <MudLink Href="@link.Url" Target="_blank" Typo="Typo.body1" Style="color: var(-- |
| | | 156 | | @link.Title |
| | | 157 | | <MudIcon Icon="@Icons.Material.Filled.OpenInNew" Size="Size.Small" Style="fo |
| | | 158 | | </MudLink> |
| | | 159 | | @if (!string.IsNullOrWhiteSpace(link.Description)) |
| | | 160 | | { |
| | | 161 | | <MudText Typo="Typo.caption" Class="mud-text-secondary">@link.Description</M |
| | | 162 | | } |
| | | 163 | | </div> |
| | | 164 | | @if (ViewModel.CanManageWorldDetails) |
| | | 165 | | { |
| | | 166 | | <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" Size="Si |
| | | 167 | | <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Si |
| | | 168 | | } |
| | | 169 | | } |
| | | 170 | | </div> |
| | | 171 | | } |
| | | 172 | | |
| | | 173 | | @if (LinksViewModel.IsAddingLink) |
| | | 174 | | { |
| | | 175 | | <div class="resource-item"> |
| | | 176 | | <MudIcon Icon="@Icons.Material.Filled.AddLink" Size="Size.Small" Style="color: var(--chr |
| | | 177 | | <MudTextField Value="@LinksViewModel.NewLinkTitle" |
| | | 178 | | ValueChanged="@((string v) => LinksViewModel.NewLinkTitle = v)" |
| | | 179 | | Placeholder="Title (e.g., Roll20 Campaign)" Variant="Variant.Outlined" Mar |
| | | 180 | | <MudTextField Value="@LinksViewModel.NewLinkUrl" |
| | | 181 | | ValueChanged="@((string v) => LinksViewModel.NewLinkUrl = v)" |
| | | 182 | | Placeholder="URL (e.g., https://roll20.net/...)" Variant="Variant.Outlined |
| | | 183 | | <MudTextField Value="@LinksViewModel.NewLinkDescription" |
| | | 184 | | ValueChanged="@((string v) => LinksViewModel.NewLinkDescription = v)" |
| | | 185 | | Placeholder="Description (optional)" Variant="Variant.Outlined" Margin="Ma |
| | | 186 | | <MudIconButton Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Smal |
| | | 187 | | OnClick="@(() => LinksViewModel.SaveNewLinkAsync())" Disabled="LinksViewM |
| | | 188 | | <MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" Size="Size.Smal |
| | | 189 | | OnClick="@(() => LinksViewModel.CancelAddLink())" /> |
| | | 190 | | </div> |
| | | 191 | | } |
| | | 192 | | </div> |
| | | 193 | | |
| | | 194 | | <MudDivider Class="my-4" /> |
| | | 195 | | |
| | | 196 | | <!-- Documents Subsection --> |
| | | 197 | | <div class="resource-subsection"> |
| | | 198 | | <div class="d-flex align-center justify-space-between mb-2"> |
| | | 199 | | <MudText Typo="Typo.subtitle1" Style="font-weight: 600;">Documents</MudText> |
| | | 200 | | @if (ViewModel.IsCurrentUserGm) |
| | | 201 | | { |
| | | 202 | | <MudButton Variant="Variant.Text" |
| | | 203 | | Color="Color.Primary" |
| | | 204 | | StartIcon="@Icons.Material.Filled.Upload" |
| | | 205 | | OnClick="@(() => DocumentsViewModel.OpenUploadDialogAsync(WorldId))" |
| | | 206 | | Size="Size.Small"> |
| | | 207 | | Upload |
| | | 208 | | </MudButton> |
| | | 209 | | } |
| | | 210 | | </div> |
| | | 211 | | |
| | | 212 | | @if (DocumentsViewModel.Documents.Count == 0) |
| | | 213 | | { |
| | | 214 | | <MudText Typo="Typo.body2" Class="mud-text-secondary"> |
| | | 215 | | No documents uploaded yet. Upload PDFs, Office files, images, or other documents. |
| | | 216 | | </MudText> |
| | | 217 | | } |
| | | 218 | | else |
| | | 219 | | { |
| | | 220 | | @foreach (var doc in DocumentsViewModel.Documents.OrderBy(d => d.Title)) |
| | | 221 | | { |
| | | 222 | | <div class="resource-item"> |
| | | 223 | | @if (DocumentsViewModel.EditingDocumentId == doc.Id) |
| | | 224 | | { |
| | | 225 | | <MudIcon Icon="@WorldDocumentsViewModel.GetDocumentIcon(doc.ContentType)" Size=" |
| | | 226 | | <MudTextField Value="@DocumentsViewModel.EditDocumentTitle" |
| | | 227 | | ValueChanged="@((string v) => DocumentsViewModel.EditDocumentTitle |
| | | 228 | | Placeholder="Title" Variant="Variant.Outlined" Margin="Margin.Dens |
| | | 229 | | <MudTextField Value="@DocumentsViewModel.EditDocumentDescription" |
| | | 230 | | ValueChanged="@((string v) => DocumentsViewModel.EditDocumentDescr |
| | | 231 | | Placeholder="Description (optional)" Variant="Variant.Outlined" Ma |
| | | 232 | | <MudIconButton Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="S |
| | | 233 | | OnClick="@(() => DocumentsViewModel.SaveDocumentEditAsync())" Dis |
| | | 234 | | <MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" Size="S |
| | | 235 | | OnClick="@(() => DocumentsViewModel.CancelDocumentEdit())" /> |
| | | 236 | | } |
| | | 237 | | else |
| | | 238 | | { |
| | | 239 | | <MudIcon Icon="@WorldDocumentsViewModel.GetDocumentIcon(doc.ContentType)" Size=" |
| | | 240 | | <div style="flex: 1; min-width: 0;"> |
| | | 241 | | <MudText Typo="Typo.body2" Style="font-weight: 500;">@doc.Title</MudText> |
| | | 242 | | @if (!string.IsNullOrEmpty(doc.Description)) |
| | | 243 | | { |
| | | 244 | | <MudText Typo="Typo.caption" Class="mud-text-secondary">@doc.Description |
| | | 245 | | } |
| | | 246 | | <MudText Typo="Typo.caption" Class="mud-text-secondary"> |
| | | 247 | | @WorldDocumentsViewModel.FormatFileSize(doc.FileSizeBytes) • Uploaded @d |
| | | 248 | | </MudText> |
| | | 249 | | </div> |
| | | 250 | | <MudIconButton Icon="@Icons.Material.Filled.Download" Color="Color.Primary" Size |
| | | 251 | | OnClick="@(() => DocumentsViewModel.DownloadDocumentAsync(doc.Id) |
| | | 252 | | @if (ViewModel.IsCurrentUserGm) |
| | | 253 | | { |
| | | 254 | | <MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" Size |
| | | 255 | | OnClick="@(() => DocumentsViewModel.StartEditDocument(doc))" |
| | | 256 | | <MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size |
| | | 257 | | OnClick="@(() => DocumentsViewModel.DeleteDocumentAsync(doc.I |
| | | 258 | | } |
| | | 259 | | } |
| | | 260 | | </div> |
| | | 261 | | } |
| | | 262 | | } |
| | | 263 | | </div> |
| | | 264 | | </div> |
| | | 265 | | </MudTabPanel> |
| | | 266 | | |
| | | 267 | | <!-- Members & Sharing Tab --> |
| | | 268 | | <MudTabPanel Text="Members & Sharing" Icon="@Icons.Material.Filled.People"> |
| | | 269 | | <WorldMembersPanel WorldId="WorldId" |
| | | 270 | | CurrentUserId="ViewModel.CurrentUserId" |
| | | 271 | | IsCurrentUserGM="ViewModel.IsCurrentUserGm" |
| | | 272 | | OnMembersChanged="@(() => ViewModel.OnMembersChangedAsync())" /> |
| | | 273 | | |
| | | 274 | | <MudDivider Class="my-4" /> |
| | | 275 | | |
| | | 276 | | <!-- Public Sharing Section --> |
| | | 277 | | <div class="sharing-section"> |
| | | 278 | | <MudText Typo="Typo.subtitle1" Class="mb-3" Style="font-weight: 600;">Public Sharing</MudText> |
| | | 279 | | |
| | | 280 | | <div class="pa-3 rounded" style="background: var(--mud-palette-background-grey);"> |
| | | 281 | | <div class="d-flex align-center gap-3 mb-3"> |
| | | 282 | | <MudSwitch Value="@SharingViewModel.IsPublic" |
| | | 283 | | ValueChanged="@((bool v) => { SharingViewModel.IsPublic = v; SharingViewModel.OnP |
| | | 284 | | Color="Color.Primary" |
| | | 285 | | Label="Make this world publicly accessible" |
| | | 286 | | T="bool" |
| | | 287 | | Disabled="@(!ViewModel.CanManageWorldDetails)" /> |
| | | 288 | | </div> |
| | | 289 | | |
| | | 290 | | @if (SharingViewModel.IsPublic) |
| | | 291 | | { |
| | | 292 | | <MudText Typo="Typo.body2" Class="mud-text-secondary mb-3"> |
| | | 293 | | Anyone with the link can view articles marked as "Public". Members-only and private arti |
| | | 294 | | </MudText> |
| | | 295 | | |
| | | 296 | | <div class="d-flex align-center gap-2 mb-2"> |
| | | 297 | | <MudText Typo="Typo.body2" Style="white-space: nowrap;">@SharingViewModel.GetPublicUrlBa |
| | | 298 | | <MudTextField Value="@SharingViewModel.PublicSlug" |
| | | 299 | | ValueChanged="@((string v) => { SharingViewModel.PublicSlug = v; _ = Shari |
| | | 300 | | Placeholder="your-world-slug" |
| | | 301 | | Variant="Variant.Outlined" |
| | | 302 | | Margin="Margin.Dense" |
| | | 303 | | Immediate="true" |
| | | 304 | | Style="max-width: 300px;" |
| | | 305 | | Disabled="@(SharingViewModel.IsCheckingSlug || !ViewModel.CanManageWorldDe |
| | | 306 | | Error="@(!string.IsNullOrEmpty(SharingViewModel.SlugError))" |
| | | 307 | | ErrorText="@SharingViewModel.SlugError" |
| | | 308 | | HelperText="@SharingViewModel.SlugHelperText" /> |
| | | 309 | | @if (SharingViewModel.IsCheckingSlug) |
| | | 310 | | { |
| | | 311 | | <MudProgressCircular Size="Size.Small" Indeterminate="true" /> |
| | | 312 | | } |
| | | 313 | | else if (SharingViewModel.SlugIsAvailable && !string.IsNullOrEmpty(SharingViewModel.Publ |
| | | 314 | | { |
| | | 315 | | <MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" /> |
| | | 316 | | } |
| | | 317 | | </div> |
| | | 318 | | |
| | | 319 | | @if (SharingViewModel.ShouldShowPublicPreview(ViewModel.World)) |
| | | 320 | | { |
| | | 321 | | <div class="d-flex align-center gap-2 mt-3"> |
| | | 322 | | <MudTextField Value="@SharingViewModel.GetFullPublicUrl(Navigation.BaseUri, ViewMode |
| | | 323 | | Label="Public URL" |
| | | 324 | | Variant="Variant.Outlined" |
| | | 325 | | Margin="Margin.Dense" |
| | | 326 | | ReadOnly="true" |
| | | 327 | | Adornment="Adornment.End" |
| | | 328 | | AdornmentIcon="@Icons.Material.Filled.ContentCopy" |
| | | 329 | | OnAdornmentClick="CopyPublicUrl" |
| | | 330 | | Style="max-width: 500px;" /> |
| | | 331 | | <MudButton Variant="Variant.Outlined" |
| | | 332 | | Color="Color.Primary" |
| | | 333 | | StartIcon="@Icons.Material.Filled.OpenInNew" |
| | | 334 | | Href="@SharingViewModel.GetFullPublicUrl(Navigation.BaseUri, ViewModel.Wo |
| | | 335 | | Target="_blank"> |
| | | 336 | | Preview |
| | | 337 | | </MudButton> |
| | | 338 | | </div> |
| | | 339 | | } |
| | | 340 | | } |
| | | 341 | | else |
| | | 342 | | { |
| | | 343 | | <MudText Typo="Typo.body2" Class="mud-text-secondary"> |
| | | 344 | | Your world is currently private. Only you and campaign members can access it. |
| | | 345 | | </MudText> |
| | | 346 | | } |
| | | 347 | | </div> |
| | | 348 | | </div> |
| | | 349 | | </MudTabPanel> |
| | | 350 | | |
| | | 351 | | @if (ViewModel.CanViewPrivateNotes) |
| | | 352 | | { |
| | | 353 | | <MudTabPanel Text="Private Notes" Icon="@Icons.Material.Filled.Lock"> |
| | | 354 | | <MudText Typo="Typo.subtitle1" Class="mb-2" Style="font-weight: 600;"> |
| | | 355 | | World Private Notes |
| | | 356 | | </MudText> |
| | | 357 | | <MudText Typo="Typo.body2" Class="mud-text-secondary mb-3"> |
| | | 358 | | Visible only to the world owner and GMs. |
| | | 359 | | </MudText> |
| | | 360 | | <PrivateNotesTipTapEditor WorldId="WorldId" |
| | | 361 | | Value="@ViewModel.EditPrivateNotes" |
| | | 362 | | ValueChanged="@((string v) => ViewModel.EditPrivateNotes = v)" |
| | | 363 | | ReadOnly="@(!ViewModel.CanManageWorldDetails)" |
| | | 364 | | UploadContextLabel="world private notes" /> |
| | | 365 | | </MudTabPanel> |
| | | 366 | | } |
| | | 367 | | |
| | | 368 | | <!-- Settings Tab --> |
| | | 369 | | <MudTabPanel Text="Settings" Icon="@Icons.Material.Filled.Settings"> |
| | | 370 | | @if (ViewModel.IsCurrentUserGm) |
| | | 371 | | { |
| | | 372 | | <WorldResourceProviders WorldId="WorldId" /> |
| | | 373 | | } |
| | | 374 | | else |
| | | 375 | | { |
| | | 376 | | <MudAlert Severity="Severity.Info"> |
| | | 377 | | Only the world owner can manage settings. |
| | | 378 | | </MudAlert> |
| | | 379 | | } |
| | | 380 | | </MudTabPanel> |
| | | 381 | | </MudTabs> |
| | | 382 | | |
| | | 383 | | <!-- Save Status --> |
| | | 384 | | <div class="chronicis-flex-between mt-4"> |
| | | 385 | | <SaveStatusIndicator IsSaving="ViewModel.IsSaving" HasUnsavedChanges="ViewModel.HasUnsavedChanges" /> |
| | | 386 | | <MudButton Variant="Variant.Filled" |
| | | 387 | | Color="Color.Primary" |
| | | 388 | | OnClick="@(() => ViewModel.SaveAsync(SharingViewModel))" |
| | | 389 | | Disabled="@(ViewModel.IsSaving || !ViewModel.CanManageWorldDetails)" |
| | | 390 | | StartIcon="@Icons.Material.Filled.Save"> |
| | | 391 | | Save |
| | | 392 | | </MudButton> |
| | | 393 | | </div> |
| | | 394 | | </MudPaper> |
| | | 395 | | } |
| | | 396 | | |
| | | 397 | | @code { |
| | | 398 | | [Parameter] |
| | | 399 | | public Guid WorldId { get; set; } |
| | | 400 | | |
| | | 401 | | protected override async Task OnParametersSetAsync() |
| | | 402 | | { |
| | | 403 | | ViewModel.PropertyChanged += OnViewModelChanged; |
| | | 404 | | LinksViewModel.PropertyChanged += OnViewModelChanged; |
| | | 405 | | DocumentsViewModel.PropertyChanged += OnViewModelChanged; |
| | | 406 | | SharingViewModel.PropertyChanged += OnViewModelChanged; |
| | | 407 | | |
| | | 408 | | await ViewModel.LoadAsync(WorldId, SharingViewModel, LinksViewModel, DocumentsViewModel); |
| | | 409 | | if (ViewModel.World != null && AppContext.CurrentWorldId != ViewModel.World.Id) |
| | | 410 | | { |
| | | 411 | | await AppContext.SelectWorldAsync(ViewModel.World.Id); |
| | | 412 | | } |
| | | 413 | | } |
| | | 414 | | |
| | | 415 | | private async Task CopyPublicUrl() |
| | | 416 | | { |
| | | 417 | | var url = SharingViewModel.GetFullPublicUrl(Navigation.BaseUri, ViewModel.World); |
| | | 418 | | if (string.IsNullOrEmpty(url)) return; |
| | | 419 | | try |
| | | 420 | | { |
| | | 421 | | await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", url); |
| | | 422 | | } |
| | | 423 | | catch { /* clipboard not available in all contexts */ } |
| | | 424 | | } |
| | | 425 | | |
| | | 426 | | private void OnViewModelChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) |
| | 9 | 427 | | => InvokeAsync(StateHasChanged); |
| | | 428 | | |
| | | 429 | | public void Dispose() |
| | | 430 | | { |
| | 3 | 431 | | ViewModel.PropertyChanged -= OnViewModelChanged; |
| | 3 | 432 | | LinksViewModel.PropertyChanged -= OnViewModelChanged; |
| | 3 | 433 | | DocumentsViewModel.PropertyChanged -= OnViewModelChanged; |
| | 3 | 434 | | SharingViewModel.PropertyChanged -= OnViewModelChanged; |
| | 3 | 435 | | } |
| | | 436 | | } |