818 lines
21 KiB
TypeScript
818 lines
21 KiB
TypeScript
import { create } from "zustand";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import {
|
|
PersonalityStore,
|
|
PersonalityDataType,
|
|
SectionItemType,
|
|
TextType,
|
|
ButtonType,
|
|
ImageType,
|
|
SocialType,
|
|
HorizontalAlignment,
|
|
VerticalAlignment,
|
|
SelectedElement,
|
|
} from "../types/Types";
|
|
|
|
// Helper function to get sections sorted by sortNumber
|
|
export const getSortedSectionKeys = (data: PersonalityDataType): string[] => {
|
|
return Object.keys(data).sort((a, b) => {
|
|
const sortA = data[a].sortNumber || 0;
|
|
const sortB = data[b].sortNumber || 0;
|
|
return sortA - sortB;
|
|
});
|
|
};
|
|
|
|
export const usePersonalityStore = create<PersonalityStore>((set, get) => ({
|
|
data: {
|
|
header: { name: "هدر", columnsCount: 1, items: [], sortNumber: 1 },
|
|
content: {
|
|
name: "محتوا",
|
|
columnsCount: 1,
|
|
items: [],
|
|
isContent: true,
|
|
sortNumber: 2,
|
|
},
|
|
footer: { name: "فوتر", columnsCount: 1, items: [], sortNumber: 3 },
|
|
},
|
|
|
|
activeSection: "",
|
|
activeItemIndex: 0,
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
|
|
setData: (data: PersonalityDataType) =>
|
|
set(() => {
|
|
// Normalize data to ensure all sections have names
|
|
const normalizedData: PersonalityDataType = {};
|
|
|
|
Object.entries(data).forEach(([key, section]) => {
|
|
normalizedData[key] = {
|
|
...section,
|
|
name:
|
|
section.name ||
|
|
(key === "header"
|
|
? "هدر"
|
|
: key === "content"
|
|
? "محتوا"
|
|
: key === "footer"
|
|
? "فوتر"
|
|
: `سکشن ${key}`),
|
|
};
|
|
});
|
|
|
|
return { data: normalizedData };
|
|
}),
|
|
|
|
setActiveSection: (section: string) =>
|
|
set((state) => {
|
|
const sectionData = state.data[section];
|
|
|
|
if (!sectionData) {
|
|
return state;
|
|
}
|
|
|
|
// اگر بخش آیتم ندارد و تعداد ستونها بیشتر از صفر است، یک آیتم پیشفرض بساز
|
|
if (sectionData.items.length === 0 && sectionData.columnsCount > 0) {
|
|
const defaultItem = {
|
|
id: uuidv4(),
|
|
backgroundColor: "#ffffff",
|
|
texts: [],
|
|
buttons: [],
|
|
images: [],
|
|
socials: [],
|
|
alignment: HorizontalAlignment.CENTER,
|
|
verticalAlignment: VerticalAlignment.MIDDLE,
|
|
};
|
|
|
|
return {
|
|
activeSection: section,
|
|
activeItemIndex: 0,
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
data: {
|
|
...state.data,
|
|
[section]: {
|
|
...sectionData,
|
|
items: [defaultItem],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
activeSection: section,
|
|
activeItemIndex: 0,
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
};
|
|
}),
|
|
|
|
setActiveItemIndex: (index: number) =>
|
|
set(() => {
|
|
return {
|
|
activeItemIndex: index,
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
};
|
|
}),
|
|
|
|
setSelectedElement: (element: SelectedElement) =>
|
|
set({ selectedElement: element, isEditMode: !!element }),
|
|
|
|
setEditMode: (isEdit: boolean) => set({ isEditMode: isEdit }),
|
|
|
|
addSection: (afterSectionKey?: string) =>
|
|
set((state) => {
|
|
// Generate automatic section name
|
|
const existingSectionNumbers = Object.values(state.data)
|
|
.map((section) => {
|
|
if (!section.name) return 0;
|
|
const match = section.name.match(/سکشن (\d+)/);
|
|
return match ? parseInt(match[1]) : 0;
|
|
})
|
|
.filter((num) => num > 0);
|
|
|
|
const nextNumber =
|
|
existingSectionNumbers.length > 0
|
|
? Math.max(...existingSectionNumbers) + 1
|
|
: 1;
|
|
|
|
const sectionName = `سکشن ${nextNumber}`;
|
|
const sectionKey = `section_${nextNumber}`;
|
|
|
|
if (state.data[sectionKey]) {
|
|
return state; // Section already exists
|
|
}
|
|
|
|
// Calculate the sortNumber for the new section
|
|
let newSortNumber = 1;
|
|
if (afterSectionKey) {
|
|
const afterSection = state.data[afterSectionKey];
|
|
if (afterSection) {
|
|
// Insert after the specified section, so sortNumber should be higher
|
|
newSortNumber = afterSection.sortNumber + 1;
|
|
|
|
// Update sortNumbers of sections that come after
|
|
Object.keys(state.data).forEach((key) => {
|
|
if (state.data[key].sortNumber > afterSection.sortNumber) {
|
|
state.data[key].sortNumber += 1;
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// Add at the end - find the highest sortNumber and add 1
|
|
const maxSortNumber = Math.max(
|
|
...Object.values(state.data).map((section) => section.sortNumber || 0)
|
|
);
|
|
newSortNumber = maxSortNumber + 1;
|
|
}
|
|
|
|
// Create new section data
|
|
const newSection = {
|
|
name: sectionName,
|
|
columnsCount: 1,
|
|
items: [],
|
|
sortNumber: newSortNumber,
|
|
};
|
|
|
|
// If afterSectionKey is provided, insert after that section
|
|
if (afterSectionKey) {
|
|
const keys = Object.keys(state.data);
|
|
const afterIndex = keys.indexOf(afterSectionKey);
|
|
// TODO all index expect that increase +
|
|
if (afterIndex !== -1) {
|
|
const newData: typeof state.data = {};
|
|
|
|
// Add sections before the target
|
|
for (let i = 0; i <= afterIndex; i++) {
|
|
newData[keys[i]] = state.data[keys[i]];
|
|
}
|
|
|
|
// Add new section
|
|
newData[sectionKey] = newSection;
|
|
|
|
// Add remaining sections
|
|
for (let i = afterIndex + 1; i < keys.length; i++) {
|
|
newData[keys[i]] = state.data[keys[i]];
|
|
}
|
|
|
|
return { data: newData };
|
|
}
|
|
}
|
|
|
|
// Default: add at the end
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: newSection,
|
|
},
|
|
};
|
|
}),
|
|
|
|
removeSection: (sectionKey: string) =>
|
|
set((state) => {
|
|
// Cannot remove content section
|
|
if (state.data[sectionKey]?.isContent) {
|
|
return state;
|
|
}
|
|
|
|
const newData = { ...state.data };
|
|
delete newData[sectionKey];
|
|
|
|
return {
|
|
data: newData,
|
|
activeSection:
|
|
state.activeSection === sectionKey ? "" : state.activeSection,
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
};
|
|
}),
|
|
|
|
setColumnsCount: (sectionKey: string, columnsCount: number) =>
|
|
set((state) => {
|
|
const currentItems = state.data[sectionKey].items;
|
|
const currentActiveItem = currentItems[state.activeItemIndex];
|
|
|
|
const newItems = Array.from({ length: columnsCount }, (_, index) => {
|
|
// اگر این آیتم در حال حاضر وجود دارد، آن را حفظ کن
|
|
if (currentItems[index]) {
|
|
return currentItems[index];
|
|
}
|
|
|
|
// اگر این اولین آیتم است و آیتم فعلی وجود دارد، از آن استفاده کن
|
|
if (index === 0 && currentActiveItem) {
|
|
return {
|
|
...currentActiveItem,
|
|
id: currentActiveItem.id || uuidv4(),
|
|
};
|
|
}
|
|
|
|
// در غیر این صورت، یک آیتم جدید بساز
|
|
return {
|
|
id: uuidv4(),
|
|
backgroundColor: "#ffffff",
|
|
texts: [],
|
|
buttons: [],
|
|
images: [],
|
|
socials: [],
|
|
alignment: HorizontalAlignment.CENTER,
|
|
verticalAlignment: VerticalAlignment.MIDDLE,
|
|
};
|
|
});
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
columnsCount,
|
|
items: newItems,
|
|
},
|
|
},
|
|
activeItemIndex: 0,
|
|
};
|
|
}),
|
|
|
|
setSectionPadding: (sectionKey: string, padding: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
padding,
|
|
},
|
|
},
|
|
})),
|
|
|
|
setSectionHeight: (sectionKey: string, height: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
height,
|
|
},
|
|
},
|
|
})),
|
|
|
|
setSectionBorder: (
|
|
sectionKey: string,
|
|
border: {
|
|
width?: string;
|
|
color?: string;
|
|
style?: "solid" | "dashed" | "dotted" | "none";
|
|
sides?: ("top" | "bottom" | "left" | "right")[];
|
|
}
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
border,
|
|
},
|
|
},
|
|
})),
|
|
|
|
setItems: (sectionKey: string, items: SectionItemType[]) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items,
|
|
},
|
|
},
|
|
})),
|
|
|
|
addItem: (sectionKey: string, item: Omit<SectionItemType, "id">) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: [...state.data[sectionKey].items, { ...item, id: uuidv4() }],
|
|
},
|
|
},
|
|
})),
|
|
|
|
updateActiveItem: (updates: Partial<SectionItemType>) =>
|
|
set((state) => {
|
|
if (!state.activeSection) return state;
|
|
|
|
const sectionKey = state.activeSection;
|
|
const items = state.data[sectionKey].items;
|
|
const activeIndex = state.activeItemIndex;
|
|
|
|
// اگر آیتم فعال وجود ندارد، یکی بساز
|
|
if (activeIndex >= items.length) {
|
|
const newItem = {
|
|
id: uuidv4(),
|
|
backgroundColor: "#ffffff",
|
|
texts: [],
|
|
buttons: [],
|
|
images: [],
|
|
socials: [],
|
|
alignment: HorizontalAlignment.CENTER,
|
|
verticalAlignment: VerticalAlignment.MIDDLE,
|
|
...updates,
|
|
};
|
|
|
|
const newItems = [...items];
|
|
newItems[activeIndex] = newItem;
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: newItems,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
const updatedItems = items.map((item, index) =>
|
|
index === activeIndex ? { ...item, ...updates } : item
|
|
);
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: updatedItems,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
updateItem: (
|
|
sectionKey: string,
|
|
id: string,
|
|
updates: Partial<SectionItemType>
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === id ? { ...item, ...updates } : item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
removeItem: (sectionKey: string, id: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.filter((item) => item.id !== id),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addText: (sectionKey: string, itemId: string, text: Omit<TextType, "id">) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
texts: [...(item.texts || []), { ...text, id: uuidv4() }],
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addTextToActiveItem: (text: Omit<TextType, "id">) =>
|
|
set((state) => {
|
|
if (!state.activeSection) return state;
|
|
|
|
const sectionKey = state.activeSection as string;
|
|
const items = state.data[sectionKey].items;
|
|
const activeIndex = state.activeItemIndex;
|
|
|
|
if (activeIndex >= items.length) return state;
|
|
|
|
const updatedItems = items.map((item, index) =>
|
|
index === activeIndex
|
|
? {
|
|
...item,
|
|
texts: [...(item.texts || []), { ...text, id: uuidv4() }],
|
|
}
|
|
: item
|
|
);
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: updatedItems,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
addButton: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
button: Omit<ButtonType, "id">
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
buttons: [
|
|
...(item.buttons || []),
|
|
{ ...button, id: uuidv4() },
|
|
],
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addButtonToActiveItem: (button: Omit<ButtonType, "id">) =>
|
|
set((state) => {
|
|
if (!state.activeSection) return state;
|
|
|
|
const sectionKey = state.activeSection as string;
|
|
const items = state.data[sectionKey].items;
|
|
const activeIndex = state.activeItemIndex;
|
|
|
|
if (activeIndex >= items.length) return state;
|
|
|
|
const updatedItems = items.map((item, index) =>
|
|
index === activeIndex
|
|
? {
|
|
...item,
|
|
buttons: [...(item.buttons || []), { ...button, id: uuidv4() }],
|
|
}
|
|
: item
|
|
);
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: updatedItems,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
updateText: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
textId: string,
|
|
updates: Partial<TextType>
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
texts: (item.texts || []).map((text) =>
|
|
text.id === textId ? { ...text, ...updates } : text
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
updateButton: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
buttonId: string,
|
|
updates: Partial<ButtonType>
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
buttons: (item.buttons || []).map((button) =>
|
|
button.id === buttonId ? { ...button, ...updates } : button
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addImage: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
image: Omit<ImageType, "id">
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
images: [...(item.images || []), { ...image, id: uuidv4() }],
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addImageToActiveItem: (image: Omit<ImageType, "id">) =>
|
|
set((state) => {
|
|
if (!state.activeSection) return state;
|
|
|
|
const sectionKey = state.activeSection as string;
|
|
const items = state.data[sectionKey].items;
|
|
const activeIndex = state.activeItemIndex;
|
|
|
|
if (activeIndex >= items.length) return state;
|
|
|
|
const updatedItems = items.map((item, index) =>
|
|
index === activeIndex
|
|
? {
|
|
...item,
|
|
images: [...(item.images || []), { ...image, id: uuidv4() }],
|
|
}
|
|
: item
|
|
);
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: updatedItems,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
updateImage: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
imageId: string,
|
|
updates: Partial<ImageType>
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
images: (item.images || []).map((image) =>
|
|
image.id === imageId ? { ...image, ...updates } : image
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
deleteText: (sectionKey: string, itemId: string, textId: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
texts: (item.texts || []).filter(
|
|
(text) => text.id !== textId
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
})),
|
|
|
|
deleteButton: (sectionKey: string, itemId: string, buttonId: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
buttons: (item.buttons || []).filter(
|
|
(button) => button.id !== buttonId
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
})),
|
|
|
|
deleteImage: (sectionKey: string, itemId: string, imageId: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
images: (item.images || []).filter(
|
|
(image) => image.id !== imageId
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
})),
|
|
|
|
addSocial: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
social: Omit<SocialType, "id">
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
socials: [
|
|
...(item.socials || []),
|
|
{ ...social, id: uuidv4() },
|
|
],
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
addSocialToActiveItem: (social: Omit<SocialType, "id">) =>
|
|
set((state) => {
|
|
if (!state.activeSection) return state;
|
|
|
|
const sectionKey = state.activeSection as string;
|
|
const items = state.data[sectionKey].items;
|
|
const activeIndex = state.activeItemIndex;
|
|
|
|
if (activeIndex >= items.length) return state;
|
|
|
|
const updatedItems = items.map((item, index) =>
|
|
index === activeIndex
|
|
? {
|
|
...item,
|
|
socials: [...(item.socials || []), { ...social, id: uuidv4() }],
|
|
}
|
|
: item
|
|
);
|
|
|
|
return {
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: updatedItems,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
|
|
updateSocial: (
|
|
sectionKey: string,
|
|
itemId: string,
|
|
socialId: string,
|
|
updates: Partial<SocialType>
|
|
) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
socials: (item.socials || []).map((social) =>
|
|
social.id === socialId ? { ...social, ...updates } : social
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
})),
|
|
|
|
deleteSocial: (sectionKey: string, itemId: string, socialId: string) =>
|
|
set((state) => ({
|
|
data: {
|
|
...state.data,
|
|
[sectionKey]: {
|
|
...state.data[sectionKey],
|
|
items: state.data[sectionKey].items.map((item) =>
|
|
item.id === itemId
|
|
? {
|
|
...item,
|
|
socials: (item.socials || []).filter(
|
|
(social) => social.id !== socialId
|
|
),
|
|
}
|
|
: item
|
|
),
|
|
},
|
|
},
|
|
selectedElement: null,
|
|
isEditMode: false,
|
|
})),
|
|
|
|
getSortedSectionKeys: () => getSortedSectionKeys(get().data),
|
|
}));
|