Skip to content

Commit

Permalink
More code duplication reduction (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrod-lowe authored Sep 28, 2024
1 parent 002fda1 commit a171f9c
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 320 deletions.
28 changes: 1 addition & 27 deletions ui/public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -291,30 +291,11 @@ button:hover {
}
}

.trackable-item span {
flex-grow: 1;
}

.trackable-item input[type="checkbox"] {
margin-right: 0.5rem;
}

.info-icon {
margin-left: 0.5rem;
cursor: pointer;
}

.trackable-item-edit {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}

.trackable-item-edit input[type="text"],
.trackable-item-edit textarea {
margin-right: 1rem;
}

.item-length-controls {
display: flex;
align-items: center;
Expand All @@ -330,11 +311,6 @@ button:hover {
margin-top: 1rem;
}

.trackable-items-edit {
display: flex;
flex-direction: column;
}

.react-tooltip {
max-width: 250px; /* Adjust to the width you prefer */
white-space: normal; /* Allow text to wrap */
Expand Down Expand Up @@ -447,9 +423,7 @@ button:hover {
gap: 1rem;
}

.burnable-item,
.trackable-item,
.keyvalue-item {
.object-item {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
Expand Down
61 changes: 41 additions & 20 deletions ui/src/baseSection.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
import React, { useState, useEffect } from 'react';
import { SheetSection, UpdateSectionInput } from "../../appsync/graphql";
import { updateSectionMutation } from "../../appsync/schema";
import { FormattedMessage, useIntl } from 'react-intl';
import { FaPencilAlt } from 'react-icons/fa';
import { useToast } from './notificationToast';
import { generateClient } from "aws-amplify/api";
import { GraphQLResult } from "@aws-amplify/api-graphql";
import { generateClient, GraphQLResult } from 'aws-amplify/api';
import { updateSectionMutation } from '../../appsync/schema';

interface BaseSectionProps<T> {
export interface BaseSectionItem {
id: string;
name: string;
description: string;
}

export interface BaseSectionContent<T extends BaseSectionItem> {
showEmpty: boolean;
items: T[];
}

interface BaseSectionProps<T extends BaseSectionItem> {
section: SheetSection;
userSubject: string;
onUpdate: (updatedSection: SheetSection) => void;
renderItems: (content: T, userSubject: string, sectionUserId: string, setContent: React.Dispatch<React.SetStateAction<T>>) => React.ReactNode;
renderEditForm: (content: T, setContent: React.Dispatch<React.SetStateAction<T>>) => React.ReactNode;
renderItems: (content: BaseSectionContent<T>, userSubject: string, sectionUserId: string, setContent: React.Dispatch<React.SetStateAction<BaseSectionContent<T>>>) => React.ReactNode;
renderEditForm: (content: BaseSectionContent<T>, setContent: React.Dispatch<React.SetStateAction<BaseSectionContent<T>>>) => React.ReactNode;
}

export const BaseSection = <T extends Record<string, any>>({
export const BaseSection = <T extends BaseSectionItem>({
section,
userSubject,
onUpdate,
renderItems,
renderEditForm
}: BaseSectionProps<T>) => {
const [isEditing, setIsEditing] = useState(false);
const [content, setContent] = useState<T>(JSON.parse(section.content || '{}') as T);
const [content, setContent] = useState<BaseSectionContent<T>>(JSON.parse(section.content || '{}'));
const [sectionName, setSectionName] = useState(section.sectionName);
const [originalContent, setOriginalContent] = useState(content);
const [originalSectionName, setOriginalSectionName] = useState(section.sectionName);
Expand All @@ -32,28 +42,39 @@ export const BaseSection = <T extends Record<string, any>>({

useEffect(() => {
setSectionName(section.sectionName);
setContent(JSON.parse(section.content || '{}') as T);
setContent(JSON.parse(section.content || '{}'));
setOriginalContent(content);
}, [section]);

const updateSection = async (updatedSection: SheetSection): Promise<SheetSection> => {
const input: UpdateSectionInput = {
gameId: updatedSection.gameId,
sectionId: updatedSection.sectionId,
sectionName: updatedSection.sectionName,
content: updatedSection.content,
};

const client = generateClient();
const response = await client.graphql({
query: updateSectionMutation,
variables: { input },
}) as GraphQLResult<{ updateSection: SheetSection }>;

return response.data.updateSection;
}

const handleUpdate = async () => {
try {
const input: UpdateSectionInput = {
gameId: section.gameId,
sectionId: section.sectionId,
const updatedSection = await updateSection({
...section,
sectionName: sectionName,
content: JSON.stringify(content),
};
const client = generateClient();
const response = await client.graphql({
query: updateSectionMutation,
variables: { input },
}) as GraphQLResult<{ updateSection: SheetSection }>;
onUpdate(response.data.updateSection);
});
onUpdate(updatedSection);
setIsEditing(false);
} catch (error) {
console.error("Error updating section:", error);
toast.addToast(intl.formatMessage({ id: "section.updateError" }), 'error');
toast.addToast(intl.formatMessage({ id: "sectionObject.updateError" }), 'error');
}
};

Expand Down
45 changes: 45 additions & 0 deletions ui/src/components/SectionEditForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { BaseSectionItem, BaseSectionContent } from '../baseSection';

interface SectionEditFormProps<T extends BaseSectionItem> {
readonly content: BaseSectionContent<T>;
readonly setContent: React.Dispatch<React.SetStateAction<BaseSectionContent<T>>>;
readonly renderItemEdit: (item: T, index: number) => React.ReactNode;
readonly addItem: () => void;
readonly removeItem: (index: number) => void;
}

export function SectionEditForm<T extends BaseSectionItem>({
content,
setContent,
renderItemEdit,
addItem,
removeItem
}: Readonly<SectionEditFormProps<T>>) {
return (
<div className={`${content.constructor.name.toLowerCase()}-items-edit`}>
{content.items.map((item, index) => (
<div key={item.id} className={`${content.constructor.name.toLowerCase()}-item-edit`}>
{renderItemEdit(item, index)}
<button onClick={() => removeItem(index)}>
<FormattedMessage id={`sectionObject.removeItem`} />
</button>
</div>
))}
<button onClick={addItem}>
<FormattedMessage id={`sectionObject.addItem`} />
</button>
<div className="show-zeros-toggle">
<label>
<input
type="checkbox"
checked={content.showEmpty}
onChange={() => setContent({ ...content, showEmpty: !content.showEmpty })}
/>
<FormattedMessage id={`sectionObject.showEmpty`} />
</label>
</div>
</div>
);
}
24 changes: 24 additions & 0 deletions ui/src/components/SectionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { FaInfoCircle } from 'react-icons/fa';
import { Tooltip } from 'react-tooltip';
import { BaseSectionItem } from '../baseSection';

interface SectionItemProps<T extends BaseSectionItem> {
readonly item: T;
readonly renderContent: (item: T) => React.ReactNode;
}

export function SectionItem<T extends BaseSectionItem>({ item, renderContent }: Readonly<SectionItemProps<T>>) {
return (
<div className={`section-item ${item.constructor.name.toLowerCase()}-item`}>
<span>{item.name}</span>
{renderContent(item)}
<FaInfoCircle
className="info-icon"
data-tooltip-content={item.description}
data-tooltip-id={`description-tooltip-${item.id}`}
/>
<Tooltip id={`description-tooltip-${item.id}`} place="top" />
</div>
);
}
Loading

0 comments on commit a171f9c

Please sign in to comment.