rename directory entries
This commit is contained in:
@@ -5,6 +5,7 @@ use crate::domain::warren::models::file::{
|
|||||||
AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError,
|
AbsoluteFilePath, CreateDirectoryError, CreateDirectoryRequest, CreateFileError,
|
||||||
CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError,
|
CreateFileRequest, DeleteDirectoryError, DeleteDirectoryRequest, DeleteFileError,
|
||||||
DeleteFileRequest, FileName, FilePath, ListFilesError, ListFilesRequest, RelativeFilePath,
|
DeleteFileRequest, FileName, FilePath, ListFilesError, ListFilesRequest, RelativeFilePath,
|
||||||
|
RenameEntryError, RenameEntryRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Warren;
|
use super::Warren;
|
||||||
@@ -310,3 +311,53 @@ impl UploadFile {
|
|||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct RenameWarrenEntryRequest {
|
||||||
|
warren_id: Uuid,
|
||||||
|
path: AbsoluteFilePath,
|
||||||
|
new_name: FileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenameWarrenEntryRequest {
|
||||||
|
pub fn new(warren_id: Uuid, path: AbsoluteFilePath, new_name: FileName) -> Self {
|
||||||
|
Self {
|
||||||
|
warren_id,
|
||||||
|
path,
|
||||||
|
new_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warren_id(&self) -> &Uuid {
|
||||||
|
&self.warren_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &AbsoluteFilePath {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_name(&self) -> &FileName {
|
||||||
|
&self.new_name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_fs_request(self, warren: &Warren) -> RenameEntryRequest {
|
||||||
|
let path = warren.path().clone().join(&self.path.to_relative());
|
||||||
|
RenameEntryRequest::new(path, self.new_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<FetchWarrenRequest> for &RenameWarrenEntryRequest {
|
||||||
|
fn into(self) -> FetchWarrenRequest {
|
||||||
|
FetchWarrenRequest::new(self.warren_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RenameWarrenEntryError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Fetch(#[from] FetchWarrenError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Rename(#[from] RenameEntryError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static {
|
|||||||
|
|
||||||
fn record_warren_file_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_warren_file_deletion_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_warren_file_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_warren_file_deletion_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_warren_entry_rename_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_warren_entry_rename_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
pub trait FileSystemMetrics: Clone + Send + Sync + 'static {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use super::models::{
|
|||||||
CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError,
|
CreateWarrenDirectoryError, CreateWarrenDirectoryRequest, DeleteWarrenDirectoryError,
|
||||||
DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest,
|
DeleteWarrenDirectoryRequest, DeleteWarrenFileError, DeleteWarrenFileRequest,
|
||||||
FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest,
|
FetchWarrenError, FetchWarrenRequest, ListWarrenFilesError, ListWarrenFilesRequest,
|
||||||
ListWarrensError, ListWarrensRequest, UploadWarrenFilesError, UploadWarrenFilesRequest,
|
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest,
|
||||||
Warren,
|
UploadWarrenFilesError, UploadWarrenFilesRequest, Warren,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,6 +55,11 @@ pub trait WarrenService: Clone + Send + Sync + 'static {
|
|||||||
&self,
|
&self,
|
||||||
request: DeleteWarrenFileRequest,
|
request: DeleteWarrenFileRequest,
|
||||||
) -> impl Future<Output = Result<FilePath, DeleteWarrenFileError>> + Send;
|
) -> impl Future<Output = Result<FilePath, DeleteWarrenFileError>> + Send;
|
||||||
|
|
||||||
|
fn rename_warren_entry(
|
||||||
|
&self,
|
||||||
|
request: RenameWarrenEntryRequest,
|
||||||
|
) -> impl Future<Output = Result<FilePath, RenameWarrenEntryError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::domain::warren::models::{
|
use crate::domain::warren::models::{
|
||||||
file::{File, FilePath},
|
file::{AbsoluteFilePath, File, FilePath},
|
||||||
warren::Warren,
|
warren::Warren,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,6 +44,13 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
|
|||||||
warren: &Warren,
|
warren: &Warren,
|
||||||
path: &FilePath,
|
path: &FilePath,
|
||||||
) -> impl Future<Output = ()> + Send;
|
) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn warren_entry_renamed(
|
||||||
|
&self,
|
||||||
|
warren: &Warren,
|
||||||
|
old_path: &AbsoluteFilePath,
|
||||||
|
new_path: &FilePath,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use anyhow::Context;
|
|||||||
use crate::domain::warren::{
|
use crate::domain::warren::{
|
||||||
models::{
|
models::{
|
||||||
file::{File, FilePath},
|
file::{File, FilePath},
|
||||||
warren::{ListWarrensError, ListWarrensRequest},
|
warren::{
|
||||||
|
ListWarrensError, ListWarrensRequest, RenameWarrenEntryError, RenameWarrenEntryRequest,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ports::FileSystemService,
|
ports::FileSystemService,
|
||||||
};
|
};
|
||||||
@@ -230,4 +232,28 @@ where
|
|||||||
|
|
||||||
result.map_err(Into::into)
|
result.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn rename_warren_entry(
|
||||||
|
&self,
|
||||||
|
request: RenameWarrenEntryRequest,
|
||||||
|
) -> Result<FilePath, RenameWarrenEntryError> {
|
||||||
|
let warren = self.repository.fetch_warren((&request).into()).await?;
|
||||||
|
|
||||||
|
let old_path = request.path().clone();
|
||||||
|
let result = self
|
||||||
|
.fs_service
|
||||||
|
.rename_entry(request.to_fs_request(&warren))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(new_path) = result.as_ref() {
|
||||||
|
self.metrics.record_warren_entry_rename_success().await;
|
||||||
|
self.notifier
|
||||||
|
.warren_entry_renamed(&warren, &old_path, new_path)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_warren_entry_rename_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ mod delete_warren_file;
|
|||||||
mod fetch_warren;
|
mod fetch_warren;
|
||||||
mod list_warren_files;
|
mod list_warren_files;
|
||||||
mod list_warrens;
|
mod list_warrens;
|
||||||
|
mod rename_warren_entry;
|
||||||
mod upload_warren_files;
|
mod upload_warren_files;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::DefaultBodyLimit,
|
extract::DefaultBodyLimit,
|
||||||
routing::{delete, get, post},
|
routing::{delete, get, patch, post},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{domain::warren::ports::WarrenService, inbound::http::AppState};
|
use crate::{domain::warren::ports::WarrenService, inbound::http::AppState};
|
||||||
@@ -22,6 +23,7 @@ use create_warren_directory::create_warren_directory;
|
|||||||
use delete_warren_directory::delete_warren_directory;
|
use delete_warren_directory::delete_warren_directory;
|
||||||
|
|
||||||
use delete_warren_file::delete_warren_file;
|
use delete_warren_file::delete_warren_file;
|
||||||
|
use rename_warren_entry::rename_warren_entry;
|
||||||
use upload_warren_files::upload_warren_files;
|
use upload_warren_files::upload_warren_files;
|
||||||
|
|
||||||
pub fn routes<WS: WarrenService>() -> Router<AppState<WS>> {
|
pub fn routes<WS: WarrenService>() -> Router<AppState<WS>> {
|
||||||
@@ -37,4 +39,5 @@ pub fn routes<WS: WarrenService>() -> Router<AppState<WS>> {
|
|||||||
post(upload_warren_files).route_layer(DefaultBodyLimit::max(1073741824)),
|
post(upload_warren_files).route_layer(DefaultBodyLimit::max(1073741824)),
|
||||||
)
|
)
|
||||||
.route("/files/file", delete(delete_warren_file))
|
.route("/files/file", delete(delete_warren_file))
|
||||||
|
.route("/files/rename", patch(rename_warren_entry))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
use axum::{Json, extract::State, http::StatusCode};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domain::warren::{
|
||||||
|
models::{
|
||||||
|
file::{
|
||||||
|
AbsoluteFilePath, AbsoluteFilePathError, FileName, FileNameError, FilePath,
|
||||||
|
FilePathError,
|
||||||
|
},
|
||||||
|
warren::{RenameWarrenEntryError, RenameWarrenEntryRequest},
|
||||||
|
},
|
||||||
|
ports::WarrenService,
|
||||||
|
},
|
||||||
|
inbound::http::{
|
||||||
|
AppState,
|
||||||
|
responses::{ApiError, ApiSuccess},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RenameWarrenEntryHttpRequestBody {
|
||||||
|
warren_id: Uuid,
|
||||||
|
path: String,
|
||||||
|
new_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum ParseRenameWarrenEntryHttpRequestError {
|
||||||
|
#[error(transparent)]
|
||||||
|
FilePath(#[from] FilePathError),
|
||||||
|
#[error(transparent)]
|
||||||
|
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||||
|
#[error(transparent)]
|
||||||
|
FileName(#[from] FileNameError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenameWarrenEntryHttpRequestBody {
|
||||||
|
fn try_into_domain(
|
||||||
|
self,
|
||||||
|
) -> Result<RenameWarrenEntryRequest, ParseRenameWarrenEntryHttpRequestError> {
|
||||||
|
let path: AbsoluteFilePath = FilePath::new(&self.path)?.try_into()?;
|
||||||
|
let new_name = FileName::new(&self.new_name)?;
|
||||||
|
|
||||||
|
Ok(RenameWarrenEntryRequest::new(
|
||||||
|
self.warren_id,
|
||||||
|
path,
|
||||||
|
new_name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseRenameWarrenEntryHttpRequestError> for ApiError {
|
||||||
|
fn from(value: ParseRenameWarrenEntryHttpRequestError) -> Self {
|
||||||
|
match value {
|
||||||
|
ParseRenameWarrenEntryHttpRequestError::FilePath(err) => match err {
|
||||||
|
FilePathError::InvalidPath => {
|
||||||
|
ApiError::BadRequest("The file path must be valid".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParseRenameWarrenEntryHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||||
|
AbsoluteFilePathError::NotAbsolute => {
|
||||||
|
ApiError::BadRequest("The file path must be absolute".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ParseRenameWarrenEntryHttpRequestError::FileName(err) => match err {
|
||||||
|
FileNameError::Slash => {
|
||||||
|
ApiError::BadRequest("The new name must not include a slash".to_string())
|
||||||
|
}
|
||||||
|
FileNameError::Empty => {
|
||||||
|
ApiError::BadRequest("The new name must not be empty".to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RenameWarrenEntryError> for ApiError {
|
||||||
|
fn from(_: RenameWarrenEntryError) -> Self {
|
||||||
|
ApiError::InternalServerError("Internal server error".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rename_warren_entry<WS: WarrenService>(
|
||||||
|
State(state): State<AppState<WS>>,
|
||||||
|
Json(request): Json<RenameWarrenEntryHttpRequestBody>,
|
||||||
|
) -> Result<ApiSuccess<()>, ApiError> {
|
||||||
|
let domain_request = request.try_into_domain()?;
|
||||||
|
|
||||||
|
state
|
||||||
|
.warren_service
|
||||||
|
.rename_warren_entry(domain_request)
|
||||||
|
.await
|
||||||
|
.map(|_| ApiSuccess::new(StatusCode::OK, ()))
|
||||||
|
.map_err(ApiError::from)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::time::UNIX_EPOCH;
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
use anyhow::{Context, anyhow};
|
use anyhow::{Context, anyhow, bail};
|
||||||
use tokio::{fs, io::AsyncWriteExt as _};
|
use tokio::{fs, io::AsyncWriteExt as _};
|
||||||
|
|
||||||
use crate::domain::warren::{
|
use crate::domain::warren::{
|
||||||
@@ -156,6 +156,10 @@ impl FileSystem {
|
|||||||
FilePath::new(&c)?
|
FilePath::new(&c)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if fs::try_exists(&new_path).await? {
|
||||||
|
bail!("File already exists");
|
||||||
|
}
|
||||||
|
|
||||||
fs::rename(current_path, &new_path).await?;
|
fs::rename(current_path, &new_path).await?;
|
||||||
|
|
||||||
Ok(new_path)
|
Ok(new_path)
|
||||||
|
|||||||
@@ -70,6 +70,13 @@ impl WarrenMetrics for MetricsDebugLogger {
|
|||||||
async fn record_warren_file_deletion_failure(&self) {
|
async fn record_warren_file_deletion_failure(&self) {
|
||||||
log::debug!("[Metrics] Warren file deletion failed");
|
log::debug!("[Metrics] Warren file deletion failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_warren_entry_rename_success(&self) {
|
||||||
|
log::debug!("[Metrics] Warren entry rename succeeded");
|
||||||
|
}
|
||||||
|
async fn record_warren_entry_rename_failure(&self) {
|
||||||
|
log::debug!("[Metrics] Warren entry rename failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemMetrics for MetricsDebugLogger {
|
impl FileSystemMetrics for MetricsDebugLogger {
|
||||||
|
|||||||
@@ -71,6 +71,20 @@ impl WarrenNotifier for NotifierDebugLogger {
|
|||||||
warren.name(),
|
warren.name(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn warren_entry_renamed(
|
||||||
|
&self,
|
||||||
|
warren: &Warren,
|
||||||
|
old_path: &crate::domain::warren::models::file::AbsoluteFilePath,
|
||||||
|
new_path: &FilePath,
|
||||||
|
) {
|
||||||
|
log::debug!(
|
||||||
|
"[Notifier] Renamed file {} to {} in warren {}",
|
||||||
|
old_path,
|
||||||
|
new_path,
|
||||||
|
warren.name(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemNotifier for NotifierDebugLogger {
|
impl FileSystemNotifier for NotifierDebugLogger {
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import {
|
|||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
|
ContextMenuSeparator,
|
||||||
} from '@/components/ui/context-menu';
|
} from '@/components/ui/context-menu';
|
||||||
import { deleteWarrenDirectory, deleteWarrenFile } from '~/lib/api/warrens';
|
import { deleteWarrenDirectory, deleteWarrenFile } from '~/lib/api/warrens';
|
||||||
import type { DirectoryEntry } from '~/types';
|
import type { DirectoryEntry } from '~/types';
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const warrenRoute = useWarrenRoute();
|
const warrenRoute = useWarrenRoute();
|
||||||
|
const renameDialog = useRenameDirectoryDialog();
|
||||||
|
|
||||||
const { entry, disabled } = defineProps<{
|
const { entry, disabled } = defineProps<{
|
||||||
entry: DirectoryEntry;
|
entry: DirectoryEntry;
|
||||||
@@ -30,6 +31,10 @@ async function submitDelete(force: boolean = false) {
|
|||||||
|
|
||||||
deleting.value = false;
|
deleting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openRenameDialog() {
|
||||||
|
renameDialog.openDialog(entry);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -62,6 +67,13 @@ async function submitDelete(force: boolean = false) {
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
|
<ContextMenuItem @select="openRenameDialog">
|
||||||
|
<Icon name="lucide:pencil" />
|
||||||
|
Rename
|
||||||
|
</ContextMenuItem>
|
||||||
|
|
||||||
|
<ContextMenuSeparator />
|
||||||
|
|
||||||
<ContextMenuItem @select="() => submitDelete(false)">
|
<ContextMenuItem @select="() => submitDelete(false)">
|
||||||
<Icon name="lucide:trash-2" />
|
<Icon name="lucide:trash-2" />
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@@ -14,24 +14,17 @@ const warrenRoute = useWarrenRoute();
|
|||||||
const dialog = useCreateDirectoryDialog();
|
const dialog = useCreateDirectoryDialog();
|
||||||
|
|
||||||
const creating = ref(false);
|
const creating = ref(false);
|
||||||
const directoryName = ref('');
|
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
||||||
const directoryNameValid = computed(
|
|
||||||
() => directoryName.value.trim().length > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
creating.value = true;
|
creating.value = true;
|
||||||
|
|
||||||
const { success } = await createDirectory(
|
const { success } = await createDirectory(warrenRoute.value, dialog.value);
|
||||||
warrenRoute.value,
|
|
||||||
directoryName.value
|
|
||||||
);
|
|
||||||
|
|
||||||
creating.value = false;
|
creating.value = false;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
directoryName.value = '';
|
dialog.reset();
|
||||||
dialog.open = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -50,7 +43,7 @@ async function submit() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
v-model="directoryName"
|
v-model="dialog.value"
|
||||||
type="text"
|
type="text"
|
||||||
name="directory-name"
|
name="directory-name"
|
||||||
placeholder="my-awesome-directory"
|
placeholder="my-awesome-directory"
|
||||||
|
|||||||
68
frontend/components/actions/RenameEntryDialog.vue
Normal file
68
frontend/components/actions/RenameEntryDialog.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { renameWarrenEntry } from '~/lib/api/warrens';
|
||||||
|
|
||||||
|
const warrenRoute = useWarrenRoute();
|
||||||
|
const dialog = useRenameDirectoryDialog();
|
||||||
|
|
||||||
|
const renaming = ref(false);
|
||||||
|
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
if (dialog.entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renaming.value = true;
|
||||||
|
|
||||||
|
const { success } = await renameWarrenEntry(
|
||||||
|
warrenRoute.value,
|
||||||
|
dialog.entry.name,
|
||||||
|
dialog.value
|
||||||
|
);
|
||||||
|
|
||||||
|
renaming.value = false;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
dialog.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dialog v-model:open="dialog.open">
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Rename your directory</DialogTitle>
|
||||||
|
<DialogDescription
|
||||||
|
>Give your directory a new memorable name</DialogDescription
|
||||||
|
>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
v-model="dialog.value"
|
||||||
|
type="text"
|
||||||
|
name="directory-name"
|
||||||
|
placeholder="my-awesome-directory"
|
||||||
|
aria-required="true"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
:disabled="!directoryNameValid || renaming"
|
||||||
|
@click="submit"
|
||||||
|
>Rename</Button
|
||||||
|
>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
@@ -256,3 +256,49 @@ export async function uploadToWarren(
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function renameWarrenEntry(
|
||||||
|
path: string,
|
||||||
|
currentName: string,
|
||||||
|
newName: string
|
||||||
|
): Promise<{ success: boolean }> {
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let [warrenId, rest] = splitOnce(path, '/');
|
||||||
|
|
||||||
|
if (rest == null) {
|
||||||
|
rest = '/';
|
||||||
|
} else {
|
||||||
|
rest = '/' + rest + '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
rest += currentName;
|
||||||
|
|
||||||
|
const { status } = await useFetch(getApiUrl(`warrens/files/rename`), {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
warrenId,
|
||||||
|
path: rest,
|
||||||
|
newName,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status.value !== 'success') {
|
||||||
|
toast.error('Rename', {
|
||||||
|
id: 'RENAME_FILE_TOAST',
|
||||||
|
description: `Failed to rename ${currentName}`,
|
||||||
|
});
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshNuxtData('current-directory');
|
||||||
|
|
||||||
|
toast.success('Rename', {
|
||||||
|
id: 'RENAME_FILE_TOAST',
|
||||||
|
description: `Successfully renamed ${currentName} to ${newName}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DirectoryListContextMenu from '~/components/DirectoryListContextMenu.vue';
|
import DirectoryListContextMenu from '~/components/DirectoryListContextMenu.vue';
|
||||||
|
import RenameEntryDialog from '~/components/actions/RenameEntryDialog.vue';
|
||||||
import { getWarrenDirectory } from '~/lib/api/warrens';
|
import { getWarrenDirectory } from '~/lib/api/warrens';
|
||||||
|
|
||||||
const entries = useAsyncData('current-directory', () =>
|
const entries = useAsyncData('current-directory', () =>
|
||||||
@@ -8,7 +9,10 @@ const entries = useAsyncData('current-directory', () =>
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DirectoryListContextMenu class="w-full grow">
|
<div>
|
||||||
<DirectoryList v-if="entries != null" :entries="entries" />
|
<DirectoryListContextMenu class="w-full grow">
|
||||||
</DirectoryListContextMenu>
|
<DirectoryList v-if="entries != null" :entries="entries" />
|
||||||
|
</DirectoryListContextMenu>
|
||||||
|
<RenameEntryDialog />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import type { DirectoryEntry } from '~/types';
|
||||||
import type { Warren } from '~/types/warrens';
|
import type { Warren } from '~/types/warrens';
|
||||||
|
|
||||||
export const useWarrenStore = defineStore<
|
export const useWarrenStore = defineStore<
|
||||||
@@ -19,10 +20,35 @@ export const useWarrenRoute = () =>
|
|||||||
export const useCreateDirectoryDialog = defineStore('create_directory_dialog', {
|
export const useCreateDirectoryDialog = defineStore('create_directory_dialog', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
open: false,
|
open: false,
|
||||||
|
value: '',
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
openDialog() {
|
openDialog() {
|
||||||
this.open = true;
|
this.open = true;
|
||||||
},
|
},
|
||||||
|
reset() {
|
||||||
|
this.open = false;
|
||||||
|
this.value = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useRenameDirectoryDialog = defineStore('rename_directory_dialog', {
|
||||||
|
state: () => ({
|
||||||
|
open: false,
|
||||||
|
entry: null as DirectoryEntry | null,
|
||||||
|
value: '',
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
openDialog(entry: DirectoryEntry) {
|
||||||
|
this.entry = entry;
|
||||||
|
this.value = entry.name;
|
||||||
|
this.open = true;
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.open = false;
|
||||||
|
this.entry = null;
|
||||||
|
this.value = '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user