Dolls (UI WIP)
This commit is contained in:
@@ -4,6 +4,7 @@ use crate::{
|
||||
FriendRemote, FriendRequestResponseDto, FriendshipResponseDto, SendFriendRequestDto,
|
||||
UserBasicDto,
|
||||
},
|
||||
remotes::dolls::{DollsRemote, CreateDollDto, UpdateDollDto, DollDto},
|
||||
services::cursor::start_cursor_tracking,
|
||||
state::{init_app_data, FDOLL},
|
||||
};
|
||||
@@ -159,6 +160,38 @@ async fn unfriend(friend_id: String) -> Result<(), String> {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_dolls() -> Result<Vec<DollDto>, String> {
|
||||
DollsRemote::new()
|
||||
.get_dolls()
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_doll(dto: CreateDollDto) -> Result<DollDto, String> {
|
||||
DollsRemote::new()
|
||||
.create_doll(dto)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn update_doll(id: String, dto: UpdateDollDto) -> Result<DollDto, String> {
|
||||
DollsRemote::new()
|
||||
.update_doll(&id, dto)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn delete_doll(id: String) -> Result<(), String> {
|
||||
DollsRemote::new()
|
||||
.delete_doll(&id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn quit_app() -> Result<(), String> {
|
||||
let app_handle = get_app_handle();
|
||||
@@ -184,6 +217,10 @@ pub fn run() {
|
||||
accept_friend_request,
|
||||
deny_friend_request,
|
||||
unfriend,
|
||||
get_dolls,
|
||||
create_doll,
|
||||
update_doll,
|
||||
delete_doll,
|
||||
quit_app
|
||||
])
|
||||
.setup(|app| {
|
||||
|
||||
177
src-tauri/src/remotes/dolls.rs
Normal file
177
src-tauri/src/remotes/dolls.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{lock_r, services::auth::with_auth, state::FDOLL};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RemoteError {
|
||||
#[error("HTTP error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[error("JSON parse error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("{0}")]
|
||||
Api(String),
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct DollColorSchemeDto {
|
||||
pub outline: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct DollConfigurationDto {
|
||||
pub color_scheme: DollColorSchemeDto,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CreateDollDto {
|
||||
pub name: String,
|
||||
pub configuration: Option<DollConfigurationDto>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct UpdateDollDto {
|
||||
pub name: Option<String>,
|
||||
pub configuration: Option<DollConfigurationDto>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct DollDto {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub configuration: DollConfigurationDto,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
pub struct DollsRemote {
|
||||
pub base_url: String,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl DollsRemote {
|
||||
pub fn new() -> Self {
|
||||
let guard = lock_r!(FDOLL);
|
||||
Self {
|
||||
base_url: guard
|
||||
.app_config
|
||||
.api_base_url
|
||||
.as_ref()
|
||||
.expect("App configuration error")
|
||||
.clone(),
|
||||
client: guard
|
||||
.clients
|
||||
.as_ref()
|
||||
.expect("App configuration error")
|
||||
.http_client
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_dolls(&self) -> Result<Vec<DollDto>, RemoteError> {
|
||||
let url = format!("{}/dolls", self.base_url);
|
||||
tracing::info!("DollsRemote::get_dolls - Sending GET request to URL: {}", url);
|
||||
let resp = with_auth(self.client.get(url)).await.send().await?;
|
||||
|
||||
let resp = resp.error_for_status().map_err(|e| {
|
||||
tracing::error!("DollsRemote::get_dolls - HTTP error: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let text = resp.text().await.map_err(|e| {
|
||||
tracing::error!("DollsRemote::get_dolls - Failed to read response text: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let dolls: Vec<DollDto> = serde_json::from_str(&text).map_err(|e| {
|
||||
tracing::error!("DollsRemote::get_dolls - Failed to parse JSON: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
tracing::info!("DollsRemote::get_dolls - Successfully parsed {} dolls", dolls.len());
|
||||
Ok(dolls)
|
||||
}
|
||||
|
||||
pub async fn create_doll(&self, dto: CreateDollDto) -> Result<DollDto, RemoteError> {
|
||||
let url = format!("{}/dolls", self.base_url);
|
||||
tracing::info!("DollsRemote::create_doll - Sending POST request to URL: {}", url);
|
||||
|
||||
let resp = with_auth(self.client.post(url))
|
||||
.await
|
||||
.json(&dto)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let resp = resp.error_for_status().map_err(|e| {
|
||||
tracing::error!("DollsRemote::create_doll - HTTP error: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let text = resp.text().await.map_err(|e| {
|
||||
tracing::error!("DollsRemote::create_doll - Failed to read response text: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let doll: DollDto = serde_json::from_str(&text).map_err(|e| {
|
||||
tracing::error!("DollsRemote::create_doll - Failed to parse JSON: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(doll)
|
||||
}
|
||||
|
||||
pub async fn update_doll(&self, id: &str, dto: UpdateDollDto) -> Result<DollDto, RemoteError> {
|
||||
let url = format!("{}/dolls/{}", self.base_url, id);
|
||||
tracing::info!("DollsRemote::update_doll - Sending PATCH request to URL: {}", url);
|
||||
|
||||
let resp = with_auth(self.client.patch(url))
|
||||
.await
|
||||
.json(&dto)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let resp = resp.error_for_status().map_err(|e| {
|
||||
tracing::error!("DollsRemote::update_doll - HTTP error: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let text = resp.text().await.map_err(|e| {
|
||||
tracing::error!("DollsRemote::update_doll - Failed to read response text: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let doll: DollDto = serde_json::from_str(&text).map_err(|e| {
|
||||
tracing::error!("DollsRemote::update_doll - Failed to parse JSON: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(doll)
|
||||
}
|
||||
|
||||
pub async fn delete_doll(&self, id: &str) -> Result<(), RemoteError> {
|
||||
let url = format!("{}/dolls/{}", self.base_url, id);
|
||||
tracing::info!("DollsRemote::delete_doll - Sending DELETE request to URL: {}", url);
|
||||
|
||||
let resp = with_auth(self.client.delete(url)).await.send().await?;
|
||||
|
||||
resp.error_for_status().map_err(|e| {
|
||||
tracing::error!("DollsRemote::delete_doll - HTTP error: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod friends;
|
||||
pub mod user;
|
||||
pub mod dolls;
|
||||
|
||||
@@ -1,7 +1,304 @@
|
||||
<script>
|
||||
import { appData } from "../../../events/app-data";
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { DollDto } from "../../../types/bindings/DollDto";
|
||||
import type { CreateDollDto } from "../../../types/bindings/CreateDollDto";
|
||||
import type { UpdateDollDto } from "../../../types/bindings/UpdateDollDto";
|
||||
|
||||
let dolls: DollDto[] = [];
|
||||
let loading = false;
|
||||
let error: string | null = null;
|
||||
let isCreateModalOpen = false;
|
||||
let isEditModalOpen = false;
|
||||
let editingDollId: string | null = null;
|
||||
|
||||
let newDollName = "";
|
||||
let newDollColorBody = "#FFFFFF";
|
||||
let newDollColorOutline = "#000000";
|
||||
|
||||
let editDollName = "";
|
||||
let editDollColorBody = "#FFFFFF";
|
||||
let editDollColorOutline = "#000000";
|
||||
|
||||
onMount(() => {
|
||||
refreshDolls();
|
||||
});
|
||||
|
||||
async function refreshDolls() {
|
||||
loading = true;
|
||||
try {
|
||||
dolls = await invoke("get_dolls");
|
||||
} catch (e) {
|
||||
error = (e as Error)?.message ?? String(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateModal() {
|
||||
newDollName = "";
|
||||
newDollColorBody = "#FFFFFF";
|
||||
newDollColorOutline = "#000000";
|
||||
isCreateModalOpen = true;
|
||||
}
|
||||
|
||||
function closeCreateModal() {
|
||||
isCreateModalOpen = false;
|
||||
}
|
||||
|
||||
async function handleCreateDoll() {
|
||||
if (!newDollName.trim()) return;
|
||||
|
||||
try {
|
||||
const dto: CreateDollDto = {
|
||||
name: newDollName,
|
||||
configuration: {
|
||||
colorScheme: {
|
||||
body: newDollColorBody,
|
||||
outline: newDollColorOutline,
|
||||
},
|
||||
},
|
||||
};
|
||||
await invoke("create_doll", { dto });
|
||||
closeCreateModal();
|
||||
await refreshDolls();
|
||||
} catch (e) {
|
||||
error = (e as Error)?.message ?? String(e);
|
||||
}
|
||||
}
|
||||
|
||||
function openEditModal(doll: DollDto) {
|
||||
editingDollId = doll.id;
|
||||
editDollName = doll.name;
|
||||
editDollColorBody = doll.configuration.colorScheme.body;
|
||||
editDollColorOutline = doll.configuration.colorScheme.outline;
|
||||
isEditModalOpen = true;
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
isEditModalOpen = false;
|
||||
editingDollId = null;
|
||||
}
|
||||
|
||||
async function handleUpdateDoll() {
|
||||
if (!editingDollId || !editDollName.trim()) return;
|
||||
|
||||
try {
|
||||
const dto: UpdateDollDto = {
|
||||
name: editDollName,
|
||||
configuration: {
|
||||
colorScheme: {
|
||||
body: editDollColorBody,
|
||||
outline: editDollColorOutline,
|
||||
},
|
||||
},
|
||||
};
|
||||
await invoke("update_doll", { id: editingDollId, dto });
|
||||
closeEditModal();
|
||||
await refreshDolls();
|
||||
} catch (e) {
|
||||
error = (e as Error)?.message ?? String(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteDoll(id: string) {
|
||||
if (!confirm("Are you sure you want to delete this doll?")) return;
|
||||
|
||||
try {
|
||||
await invoke("delete_doll", { id });
|
||||
await refreshDolls();
|
||||
} catch (e) {
|
||||
error = (e as Error)?.message ?? String(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>{$appData?.user?.name}</p>
|
||||
<div class="dolls-page flex flex-col gap-4 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold">Your Dolls</h2>
|
||||
<button class="btn btn-primary btn-sm" on:click={openCreateModal}>
|
||||
Create Doll
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="alert alert-error">
|
||||
<span>{error}</span>
|
||||
<button class="btn btn-xs btn-ghost" on:click={() => (error = null)}
|
||||
>X</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center p-4">
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
</div>
|
||||
{:else if dolls.length === 0}
|
||||
<div class="text-center text-base-content/70 py-8">
|
||||
<p>No dolls found. Create your first doll!</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each dolls as doll (doll.id)}
|
||||
<div class="card bg-base-200 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="card-title text-base">{doll.name}</h3>
|
||||
<div class="flex gap-2 text-sm text-base-content/70">
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="w-4 h-4 rounded border border-base-content/20"
|
||||
style="background-color: {doll.configuration.colorScheme
|
||||
.body};"
|
||||
></div>
|
||||
<span>Body</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="w-4 h-4 rounded border border-base-content/20"
|
||||
style="background-color: {doll.configuration.colorScheme
|
||||
.outline};"
|
||||
></div>
|
||||
<span>Outline</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<button
|
||||
class="btn btn-xs btn-ghost"
|
||||
on:click={() => openEditModal(doll)}>Edit</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-xs btn-ghost text-error"
|
||||
on:click={() => handleDeleteDoll(doll.id)}>Delete</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Create Modal -->
|
||||
{#if isCreateModalOpen}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">Create New Doll</h3>
|
||||
<div class="form-control w-full mt-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Doll Name"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={newDollName}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control w-full mt-2">
|
||||
<label class="label">
|
||||
<span class="label-text">Body Color</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="color"
|
||||
class="input input-bordered w-12 p-1 h-10"
|
||||
bind:value={newDollColorBody}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={newDollColorBody}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control w-full mt-2">
|
||||
<label class="label">
|
||||
<span class="label-text">Outline Color</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="color"
|
||||
class="input input-bordered w-12 p-1 h-10"
|
||||
bind:value={newDollColorOutline}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={newDollColorOutline}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button class="btn" on:click={closeCreateModal}>Cancel</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={handleCreateDoll}
|
||||
disabled={!newDollName.trim()}>Create</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Edit Modal -->
|
||||
{#if isEditModalOpen}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg">Edit Doll</h3>
|
||||
<div class="form-control w-full mt-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Doll Name"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={editDollName}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control w-full mt-2">
|
||||
<label class="label">
|
||||
<span class="label-text">Body Color</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="color"
|
||||
class="input input-bordered w-12 p-1 h-10"
|
||||
bind:value={editDollColorBody}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={editDollColorBody}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control w-full mt-2">
|
||||
<label class="label">
|
||||
<span class="label-text">Outline Color</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="color"
|
||||
class="input input-bordered w-12 p-1 h-10"
|
||||
bind:value={editDollColorOutline}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
bind:value={editDollColorOutline}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button class="btn" on:click={closeEditModal}>Cancel</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={handleUpdateDoll}
|
||||
disabled={!editDollName.trim()}>Save</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
4
src/types/bindings/CreateDollDto.ts
Normal file
4
src/types/bindings/CreateDollDto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
||||
|
||||
export type CreateDollDto = { name: string, configuration: DollConfigurationDto | null, };
|
||||
3
src/types/bindings/DollColorSchemeDto.ts
Normal file
3
src/types/bindings/DollColorSchemeDto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DollColorSchemeDto = { outline: string, body: string, };
|
||||
4
src/types/bindings/DollConfigurationDto.ts
Normal file
4
src/types/bindings/DollConfigurationDto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DollColorSchemeDto } from "./DollColorSchemeDto";
|
||||
|
||||
export type DollConfigurationDto = { colorScheme: DollColorSchemeDto, };
|
||||
4
src/types/bindings/DollDto.ts
Normal file
4
src/types/bindings/DollDto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
||||
|
||||
export type DollDto = { id: string, name: string, configuration: DollConfigurationDto, createdAt: string, updatedAt: string, };
|
||||
4
src/types/bindings/UpdateDollDto.ts
Normal file
4
src/types/bindings/UpdateDollDto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DollConfigurationDto } from "./DollConfigurationDto";
|
||||
|
||||
export type UpdateDollDto = { name: string | null, configuration: DollConfigurationDto | null, };
|
||||
Reference in New Issue
Block a user