Compare commits
1 Commits
ece8536b72
...
b3ba450f63
Author | SHA1 | Date | |
---|---|---|---|
b3ba450f63 |
@ -147,10 +147,10 @@ func (h *StackHandler) deleteImageFromStack(w http.ResponseWriter, r *http.Reque
|
||||
func (h *StackHandler) deleteImageStackSchemaItem(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
stringImageID := chi.URLParam(r, "stackID")
|
||||
stringStackID := chi.URLParam(r, "stackID")
|
||||
stringSchemaItemID := chi.URLParam(r, "schemaItemID")
|
||||
|
||||
imageID, err := uuid.Parse(stringImageID)
|
||||
stackID, err := uuid.Parse(stringStackID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
@ -169,8 +169,9 @@ func (h *StackHandler) deleteImageStackSchemaItem(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
stack, err := h.stackModel.Get(ctx, schemaItemID)
|
||||
stack, err := h.stackModel.Get(ctx, stackID)
|
||||
if err != nil {
|
||||
h.logger.Error("could not get stack model", "err", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@ -185,7 +186,7 @@ func (h *StackHandler) deleteImageStackSchemaItem(w http.ResponseWriter, r *http
|
||||
// manipulations. So we could create a middleware.
|
||||
// If you repeat this 3 times, then organise it :)
|
||||
|
||||
err = h.stackModel.DeleteSchemaItem(ctx, schemaItemID, imageID)
|
||||
err = h.stackModel.DeleteSchemaItem(ctx, stackID, schemaItemID)
|
||||
if err != nil {
|
||||
h.logger.Warn("failed to delete image from list", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
@ -7,7 +7,13 @@ import {
|
||||
createResource,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import { deleteImage, deleteImageFromStack, getUserImages, JustTheImageWhatAreTheseNames } from "../network";
|
||||
import {
|
||||
deleteImage,
|
||||
deleteImageFromStack,
|
||||
deleteStackItem,
|
||||
getUserImages,
|
||||
JustTheImageWhatAreTheseNames,
|
||||
} from "../network";
|
||||
|
||||
export type SearchImageStore = {
|
||||
imagesByDate: Accessor<
|
||||
@ -21,40 +27,41 @@ export type SearchImageStore = {
|
||||
onRefetchImages: () => void;
|
||||
onDeleteImage: (imageID: string) => void;
|
||||
onDeleteImageFromStack: (stackID: string, imageID: string) => void;
|
||||
onDeleteStackItem: (stackID: string, schemaItemID: string) => void;
|
||||
};
|
||||
|
||||
const SearchImageContext = createContext<SearchImageStore>();
|
||||
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
const [data, { refetch }] = createResource(getUserImages);
|
||||
|
||||
const sortedImages = createMemo<ReturnType<SearchImageStore["imagesByDate"]>>(
|
||||
() => {
|
||||
const d = data();
|
||||
if (d == null) {
|
||||
return [];
|
||||
const sortedImages = createMemo<
|
||||
ReturnType<SearchImageStore["imagesByDate"]>
|
||||
>(() => {
|
||||
const d = data();
|
||||
if (d == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sorted by day. But we could potentially add more in the future.
|
||||
const buckets: Record<string, JustTheImageWhatAreTheseNames> = {};
|
||||
|
||||
for (const image of d.UserImages) {
|
||||
if (image.CreatedAt == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sorted by day. But we could potentially add more in the future.
|
||||
const buckets: Record<string, JustTheImageWhatAreTheseNames> = {};
|
||||
|
||||
for (const image of d.UserImages) {
|
||||
if (image.CreatedAt == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const date = new Date(image.CreatedAt).toDateString();
|
||||
if (!(date in buckets)) {
|
||||
buckets[date] = [];
|
||||
}
|
||||
|
||||
buckets[date].push(image);
|
||||
const date = new Date(image.CreatedAt).toDateString();
|
||||
if (!(date in buckets)) {
|
||||
buckets[date] = [];
|
||||
}
|
||||
|
||||
return Object.entries(buckets)
|
||||
.map(([date, images]) => ({ date: new Date(date), images }))
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||
},
|
||||
);
|
||||
buckets[date].push(image);
|
||||
}
|
||||
|
||||
return Object.entries(buckets)
|
||||
.map(([date, images]) => ({ date: new Date(date), images }))
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||
});
|
||||
|
||||
return (
|
||||
<SearchImageContext.Provider
|
||||
@ -68,7 +75,10 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
},
|
||||
onDeleteImageFromStack: (stackID: string, imageID: string) => {
|
||||
deleteImageFromStack(stackID, imageID).then(refetch);
|
||||
}
|
||||
},
|
||||
onDeleteStackItem: (stackID: string, schemaItemID: string) => {
|
||||
deleteStackItem(stackID, schemaItemID).then(refetch);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
@ -121,6 +121,18 @@ export const deleteImageFromStack = async (listID: string, imageID: string): Pro
|
||||
await fetch(request);
|
||||
}
|
||||
|
||||
export const deleteStackItem = async (
|
||||
stackID: string,
|
||||
schemaItemID: string,
|
||||
): Promise<void> => {
|
||||
const request = await getBaseAuthorizedRequest({
|
||||
path: `stacks/${stackID}/${schemaItemID}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
await fetch(request);
|
||||
}
|
||||
|
||||
export const deleteList = async (listID: string): Promise<void> => {
|
||||
const request = await getBaseAuthorizedRequest({
|
||||
path: `stacks/${listID}`,
|
||||
|
@ -57,6 +57,59 @@ const DeleteButton: Component<{ onDelete: () => void }> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DeleteSchemaItemButton: Component<{
|
||||
onDelete: () => void;
|
||||
itemName: string;
|
||||
}> = (props) => {
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
aria-label="Delete schema item"
|
||||
class="text-red-600 hover:text-red-700 ml-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
<Dialog.Root open={isOpen()} onOpenChange={setIsOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class="fixed inset-0 bg-black bg-opacity-50" />
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<Dialog.Content class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
||||
<Dialog.Title class="text-lg font-bold mb-2">
|
||||
Delete Column
|
||||
</Dialog.Title>
|
||||
<Dialog.Description class="mb-4">
|
||||
Are you sure you want to delete the column "
|
||||
{props.itemName}"? This will remove this column
|
||||
and all its data from the list.
|
||||
</Dialog.Description>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Dialog.CloseButton>
|
||||
<button class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">
|
||||
Cancel
|
||||
</button>
|
||||
</Dialog.CloseButton>
|
||||
<button
|
||||
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
||||
onClick={props.onDelete}
|
||||
>
|
||||
Delete Column
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</div>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DeleteListButton: Component<{ onDelete: () => void }> = (props) => {
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
|
||||
@ -107,17 +160,22 @@ export const List: Component = () => {
|
||||
const { listId } = useParams();
|
||||
const nav = useNavigate();
|
||||
|
||||
const { stacks, onDeleteImageFromStack } = useSearchImageContext();
|
||||
const { stacks, onDeleteImageFromStack, onDeleteStackItem } =
|
||||
useSearchImageContext();
|
||||
|
||||
const [accessToken] = createResource(getAccessToken);
|
||||
|
||||
const list = () => stacks().find((l) => l.ID === listId);
|
||||
|
||||
const handleDeleteList = async () => {
|
||||
await deleteList(listId)
|
||||
await deleteList(listId);
|
||||
nav("/");
|
||||
};
|
||||
|
||||
const handleDeleteSchemaItem = (schemaItemId: string) => {
|
||||
onDeleteStackItem(listId, schemaItemId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<Show when={list()} fallback="List could not be found">
|
||||
@ -151,15 +209,25 @@ export const List: Component = () => {
|
||||
<For each={l().SchemaItems}>
|
||||
{(item, index) => (
|
||||
<th
|
||||
class={`px-6 py-4 text-left text-sm font-semibold text-neutral-900 min-w-32 ${index() <
|
||||
l().SchemaItems
|
||||
.length -
|
||||
1
|
||||
? "border-r border-neutral-200"
|
||||
: ""
|
||||
}`}
|
||||
class={`px-6 py-4 text-left text-sm font-semibold text-neutral-900 min-w-32 ${
|
||||
index() <
|
||||
l().SchemaItems.length -
|
||||
1
|
||||
? "border-r border-neutral-200"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{item.Item}
|
||||
<div class="flex items-center">
|
||||
{item.Item}
|
||||
<DeleteSchemaItemButton
|
||||
onDelete={() =>
|
||||
handleDeleteSchemaItem(
|
||||
item.ID,
|
||||
)
|
||||
}
|
||||
itemName={item.Item}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
)}
|
||||
</For>
|
||||
@ -169,10 +237,11 @@ export const List: Component = () => {
|
||||
<For each={l().Images}>
|
||||
{(image, rowIndex) => (
|
||||
<tr
|
||||
class={`hover:bg-neutral-50 transition-colors ${rowIndex() % 2 === 0
|
||||
? "bg-white"
|
||||
: "bg-neutral-25"
|
||||
}`}
|
||||
class={`hover:bg-neutral-50 transition-colors ${
|
||||
rowIndex() % 2 === 0
|
||||
? "bg-white"
|
||||
: "bg-neutral-25"
|
||||
}`}
|
||||
>
|
||||
<td class="px-6 py-4 border-r border-neutral-200">
|
||||
<div class="flex items-center gap-2">
|
||||
@ -199,13 +268,14 @@ export const List: Component = () => {
|
||||
<For each={image.Items}>
|
||||
{(item, colIndex) => (
|
||||
<td
|
||||
class={`px-6 py-4 text-sm text-neutral-700 ${colIndex() <
|
||||
class={`px-6 py-4 text-sm text-neutral-700 ${
|
||||
colIndex() <
|
||||
image.Items
|
||||
.length -
|
||||
1
|
||||
? "border-r border-neutral-200"
|
||||
: ""
|
||||
}`}
|
||||
1
|
||||
? "border-r border-neutral-200"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
class="max-w-xs truncate"
|
||||
|
Reference in New Issue
Block a user