Implemented simple image upload in ArticleEditor
This commit is contained in:
		
							parent
							
								
									06ce7e995c
								
							
						
					
					
						commit
						4dac73c564
					
				|  | @ -20,17 +20,6 @@ | |||
| 	</ul> | ||||
| </div> | ||||
| 
 | ||||
| <div class="my-3 flex flex-wrap gap-4"> | ||||
| 	@foreach (var image in Article.Images) { | ||||
| 		<figure> | ||||
| 			<img src="/images/@(image.Id)?size=400" width="400" alt="@image.ImageDescription"/> | ||||
| 			<figcaption> | ||||
| 				@image.ImageDescription | ||||
| 			</figcaption> | ||||
| 		</figure> | ||||
| 	} | ||||
| </div> | ||||
| 
 | ||||
| <EditForm method="post" FormName="article-editor" Model="@Model" OnValidSubmit="OnValidSubmit"> | ||||
| 	<DataAnnotationsValidator/> | ||||
| 	<input type="hidden" @bind-value="@Model.Id"/> | ||||
|  | @ -154,7 +143,23 @@ | |||
| 		} | ||||
| 	</div> | ||||
| </EditForm> | ||||
| <CategoryPicker/> | ||||
| 
 | ||||
| <ImageModal Id="@ImageModal" ImageAdded="ImageAdded" /> | ||||
| <div class="my-3 flex flex-wrap gap-4 min-h-24"> | ||||
| 	@foreach (var image in Article.Images) { | ||||
| 		<figure class="p-2 bg-base-200"> | ||||
| 			<img class="w-40" src="/images/@(image.Id)?size=400" width="400" | ||||
| 			     title="@image.ImageDescription" alt="@image.ImageDescription"/> | ||||
| 			<figcaption> | ||||
| 				<button type="button" class="btn btn-info w-full mt-3" | ||||
| 						onclick="navigator.clipboard.writeText(')?size=400)')"> | ||||
| 					@Localizer["Image_CopyLink"] | ||||
| 				</button> | ||||
| 			</figcaption> | ||||
| 		</figure> | ||||
| 	} | ||||
| 	<button type="button" class="btn" onclick="@(ImageModal).showModal()">@Localizer["Image_Add_Label"]</button> | ||||
| </div> | ||||
| 
 | ||||
| <SectionContent SectionName="scripts"> | ||||
| 	<script> | ||||
|  | @ -199,6 +204,8 @@ | |||
| </SectionContent> | ||||
| 
 | ||||
| @code { | ||||
| 	private const string ImageModal = "AddImage"; | ||||
| 
 | ||||
| 	[Parameter]  | ||||
|     public Guid? Id { get; set; } | ||||
| 	[Parameter]  | ||||
|  | @ -282,6 +289,14 @@ | |||
| 			Model.Categories ??= []; | ||||
| 			context.Update(Article); | ||||
| 
 | ||||
| 			var existingImages = await context.Set<Article>().Where(a => a.Id == Article.Id) | ||||
| 				.SelectMany(a => a.Images).ToListAsync(); | ||||
| 			foreach (var image in Article.Images) { | ||||
| 				context.Entry(image).State =  | ||||
| 					existingImages.Any(i => i.Id == image.Id) ?  | ||||
| 						EntityState.Modified : EntityState.Added; | ||||
| 			} | ||||
| 			 | ||||
| 			var relations = await context.Set<ArticleCategory>() | ||||
| 				.IgnoreQueryFilters().IgnoreAutoIncludes() | ||||
| 				.Where(ac => ac.Article == Article && !Model.Categories.Contains(ac.Category.Id)) | ||||
|  | @ -335,6 +350,11 @@ | |||
| 		Model.Categories = selected?.Select(Guid.Parse).ToArray(); | ||||
| 	} | ||||
| 
 | ||||
| 	private async Task ImageAdded(ArticleImage image) { | ||||
| 		Article.Images.Add(image); | ||||
| 		await InvokeAsync(StateHasChanged); | ||||
| 	} | ||||
| 
 | ||||
| 	private sealed class InputModel { | ||||
| 		public Guid? Id { get; set; } | ||||
| 
 | ||||
|  | @ -346,5 +366,4 @@ | |||
| 		public Guid[]? Categories { get; set; } | ||||
| 		public DateTimeOffset? PublishDate { get; set; } | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										69
									
								
								Wave/Components/Pages/Partials/ImageModal.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Wave/Components/Pages/Partials/ImageModal.razor
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | |||
| @using Wave.Services | ||||
| @using Wave.Data | ||||
| 
 | ||||
| @inject ImageService Images | ||||
| @inject IStringLocalizer<ArticleEditor> Localizer | ||||
| 
 | ||||
| <ModalComponent Id="@Id" ShowCloseButton="false"> | ||||
| 	<ChildContent> | ||||
| 		@if (Image != null) { | ||||
| 			<figure> | ||||
| 				<img src="/images/@(Image)" alt=""/> | ||||
| 			</figure> | ||||
| 		} | ||||
| 		<FileUploadComponent FileUploadedCallback="FileChanged"/> | ||||
| 		 | ||||
| 		<InputLabelComponent LabelText="@Localizer["Image_Description_Label"]"> | ||||
| 			<InputText class="input input-bordered w-full" maxlength="2048" | ||||
| 			           @bind-Value="@ImageDescription"  | ||||
| 			           placeholder="@Localizer["Image_Description_Placeholder"]"  | ||||
| 			           autocomplete="off" /> | ||||
| 		</InputLabelComponent> | ||||
| 	</ChildContent> | ||||
| 	<Actions> | ||||
| 		<button class="btn btn-primary" @onclick="Save">@Localizer["Image_Add_Submit"]</button> | ||||
| 		<form method="dialog"> | ||||
| 			<button type="submit" class="btn btn-error" @onclick="Cancel">@Localizer["Image_Add_Abort"]</button> | ||||
| 		</form> | ||||
| 	</Actions> | ||||
| </ModalComponent> | ||||
| 
 | ||||
| @code { | ||||
| 	[Parameter] | ||||
| 	public required string Id { get; set; } | ||||
| 	[Parameter] | ||||
| 	public required EventCallback<ArticleImage> ImageAdded { get; set; } | ||||
| 
 | ||||
| 	private Guid? Image { get; set; } | ||||
| 	private string ImageDescription { get; set; } = string.Empty; | ||||
| 	 | ||||
| 	private async Task FileChanged(string tempPath) { | ||||
| 		if (Image is { } img) { | ||||
| 			Image = null; | ||||
| 			Images.Delete(img); | ||||
| 		} | ||||
| 
 | ||||
| 		Image = await Images.StoreImageAsync(tempPath); | ||||
| 	} | ||||
| 
 | ||||
| 	private async Task Save() { | ||||
| 		if (Image is null) return; | ||||
| 
 | ||||
| 		await ImageAdded.InvokeAsync(new ArticleImage { | ||||
| 			Id = Image.Value, | ||||
| 			ImageDescription = ImageDescription | ||||
| 		}); | ||||
| 
 | ||||
| 		Image = null; | ||||
| 		ImageDescription = string.Empty; | ||||
| 	} | ||||
| 
 | ||||
| 	private void Cancel() { | ||||
| 		if (Image is { } img) { | ||||
| 			Image = null; | ||||
| 			Images.Delete(img); | ||||
| 		} | ||||
| 		ImageDescription = string.Empty; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -191,4 +191,31 @@ | |||
|   <data name="Tools_Mark_Tooltip" xml:space="preserve"> | ||||
|     <value>Den selektierten Text markieren</value> | ||||
|   </data> | ||||
|     <data name="Categories_Label" xml:space="preserve"> | ||||
|         <value>Kategorie</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Label" xml:space="preserve"> | ||||
|         <value>Bild Hochladen</value> | ||||
|     </data> | ||||
|     <data name="Image_Description_Label" xml:space="preserve"> | ||||
|         <value>Bildbeschreibung</value> | ||||
|     </data> | ||||
|     <data name="Image_Description_Placeholder" xml:space="preserve"> | ||||
|         <value>Ein Bild eines Käsekuchen</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Submit" xml:space="preserve"> | ||||
|         <value>Hinzufügen</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Abort" xml:space="preserve"> | ||||
|         <value>Schließen</value> | ||||
|     </data> | ||||
|     <data name="Save_Success" xml:space="preserve"> | ||||
|         <value>Artikel Gespeichert</value> | ||||
|     </data> | ||||
|     <data name="Save_Error" xml:space="preserve"> | ||||
|         <value>Unbekannter fehler beim speichern des Artikels</value> | ||||
|     </data> | ||||
|     <data name="Image_CopyLink" xml:space="preserve"> | ||||
|         <value>Link Kopieren</value> | ||||
|     </data> | ||||
| </root> | ||||
|  | @ -197,4 +197,31 @@ | |||
|   <data name="Tools_Mark_Tooltip" xml:space="preserve"> | ||||
|     <value>Mark the selected text</value> | ||||
|   </data> | ||||
|     <data name="Categories_Label" xml:space="preserve"> | ||||
|         <value>Category</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Label" xml:space="preserve"> | ||||
|         <value>Upload Image</value> | ||||
|     </data> | ||||
|     <data name="Image_Description_Label" xml:space="preserve"> | ||||
|         <value>Image Description</value> | ||||
|     </data> | ||||
|     <data name="Image_Description_Placeholder" xml:space="preserve"> | ||||
|         <value>A picture of a cheesecake</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Submit" xml:space="preserve"> | ||||
|         <value>Add</value> | ||||
|     </data> | ||||
|     <data name="Image_Add_Abort" xml:space="preserve"> | ||||
|         <value>Close</value> | ||||
|     </data> | ||||
|     <data name="Save_Success" xml:space="preserve"> | ||||
|         <value>Article Saved</value> | ||||
|     </data> | ||||
|     <data name="Save_Error" xml:space="preserve"> | ||||
|         <value>Unknown error saving article</value> | ||||
|     </data> | ||||
|     <data name="Image_CopyLink" xml:space="preserve"> | ||||
|         <value>Copy Link</value> | ||||
|     </data> | ||||
| </root> | ||||
							
								
								
									
										2
									
								
								Wave/wwwroot/css/main.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Wave/wwwroot/css/main.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in a new issue