Skip to content

Commit d8a1638

Browse files
committed
more
1 parent 38ad28f commit d8a1638

File tree

16 files changed

+205
-76
lines changed

16 files changed

+205
-76
lines changed

backend/conferences/admin/conference.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,13 @@ class ConferenceAdmin(
132132
"name",
133133
"code",
134134
"logo",
135+
"location",
135136
"introduction",
136137
"timezone",
137-
"latitude",
138-
"longitude",
138+
(
139+
"latitude",
140+
"longitude",
141+
),
139142
"map_link",
140143
)
141144
},
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.4 on 2024-12-20 19:34
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('conferences', '0050_alter_deadline_type'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='conference',
15+
name='location',
16+
field=models.TextField(blank=True, max_length=1024, verbose_name='location'),
17+
),
18+
]

backend/conferences/models/conference.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Conference(GeoLocalizedModel, TimeFramedModel, TimeStampedModel):
2828
code = models.CharField(_("code"), max_length=100, unique=True)
2929
timezone = TimeZoneField()
3030
logo = models.ImageField(_("logo"), upload_to=get_upload_to, blank=True)
31+
location = models.TextField(_("location"), max_length=1024, blank=True)
3132

3233
topics = models.ManyToManyField(
3334
"conferences.Topic", verbose_name=_("topics"), blank=True

backend/custom_admin/astro.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ export default defineConfig({
1919
format: "file",
2020
assetsPrefix: "/django-static/",
2121
},
22+
devToolbar: {
23+
enabled: false,
24+
},
2225
});

backend/custom_admin/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"astro": "^5.0.5",
3737
"clsx": "^2.1.1",
3838
"fabric": "^6.5.3",
39+
"fast-deep-equal": "^3.1.3",
3940
"graphql": "^16.10.0",
4041
"lucide-react": "^0.468.0",
4142
"pdfjs-dist": "^4.9.155",

backend/custom_admin/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/custom_admin/src/components/invitation-letter-document-builder/builder.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
1-
import * as Toast from "@radix-ui/react-toast";
21
import { Button, Heading, Text } from "@radix-ui/themes";
32
import { Box } from "@radix-ui/themes";
43
import { Plus } from "lucide-react";
5-
import { Fragment } from "react";
4+
import { Fragment, useEffect } from "react";
65

76
import { EditorSection } from "./editor-section";
87
import { useLocalData } from "./local-state";
98

109
export const InvitationLetterBuilder = () => {
11-
const { localData, saveChanges, isSaving, saveFailed, addPage } =
12-
useLocalData();
10+
const { isDirty, localData, saveChanges, isSaving, addPage } = useLocalData();
1311

14-
if (!localData) {
15-
return <Text>Loading...</Text>;
16-
}
12+
useEffect(() => {
13+
const listener = (e) => {
14+
if (isDirty) {
15+
e.preventDefault();
16+
e.returnValue = "";
17+
}
18+
19+
return "";
20+
};
21+
22+
window.addEventListener("beforeunload", listener);
23+
24+
return () => {
25+
window.removeEventListener("beforeunload", listener);
26+
};
27+
}, [isDirty]);
1728

1829
return (
1930
<>

backend/custom_admin/src/components/invitation-letter-document-builder/local-state.tsx

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
1-
import { useContext, useEffect, useReducer } from "react";
2-
import { useInvitationLetterDocumentQuery } from "./invitation-letter-document.generated";
1+
import equal from "fast-deep-equal";
2+
import {
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useReducer,
8+
useState,
9+
} from "react";
10+
import {
11+
useInvitationLetterDocumentQuery,
12+
useInvitationLetterDocumentSuspenseQuery,
13+
} from "./invitation-letter-document.generated";
314
import { useUpdateInvitationLetterDocumentMutation } from "./update-invitation-letter-document.generated";
415

516
import { createContext } from "react";
617

18+
type State = {
19+
header: string;
20+
footer: string;
21+
pages: {
22+
id: string;
23+
title: string;
24+
content: string;
25+
}[];
26+
};
27+
728
export const LocalStateContext = createContext<{
8-
localData: any;
29+
localData: State;
930
saveChanges: () => void;
1031
isSaving: boolean;
1132
saveFailed: boolean;
@@ -14,6 +35,7 @@ export const LocalStateContext = createContext<{
1435
removePage: (pageId: string) => void;
1536
movePageUp: (pageId: string) => void;
1637
movePageDown: (pageId: string) => void;
38+
isDirty: boolean;
1739
}>({
1840
localData: null,
1941
saveChanges: () => {},
@@ -24,6 +46,7 @@ export const LocalStateContext = createContext<{
2446
removePage: () => {},
2547
movePageUp: () => {},
2648
movePageDown: () => {},
49+
isDirty: false,
2750
});
2851

2952
enum ActionType {
@@ -35,15 +58,13 @@ enum ActionType {
3558
MovePageDown = "MOVE_PAGE_DOWN",
3659
}
3760

38-
const reducer = (state, action) => {
61+
const reducer = (state: State, action) => {
3962
switch (action.type) {
4063
case ActionType.LoadData: {
4164
return {
4265
...action.payload,
43-
__typename: undefined,
4466
pages: action.payload.pages.map((page) => ({
4567
...page,
46-
__typename: undefined,
4768
})),
4869
};
4970
}
@@ -129,14 +150,47 @@ export const useLocalData = () => {
129150
return useContext(LocalStateContext);
130151
};
131152

132-
const useLoadRemoteData = () => {
153+
const removeTypenames = (obj) => {
154+
if (Array.isArray(obj)) {
155+
return obj.map(removeTypenames);
156+
}
157+
158+
if (obj !== null && typeof obj === "object") {
159+
const newObj = {};
160+
for (const key in obj) {
161+
if (key !== "__typename") {
162+
newObj[key] = removeTypenames(obj[key]);
163+
}
164+
}
165+
166+
return newObj;
167+
}
168+
169+
return obj;
170+
};
171+
172+
const useLoadRemoteData = (dispatch) => {
133173
const documentId = (window as any).documentId;
134-
const { data: remoteData } = useInvitationLetterDocumentQuery({
174+
175+
const { data } = useInvitationLetterDocumentSuspenseQuery({
135176
variables: {
136177
id: documentId,
137178
},
138179
});
139-
return remoteData?.invitationLetterDocument?.dynamicDocument;
180+
181+
useEffect(() => {
182+
const dynamicDocument = data?.invitationLetterDocument.dynamicDocument;
183+
dispatch({
184+
type: ActionType.LoadData,
185+
payload: removeTypenames(dynamicDocument),
186+
});
187+
}, [data]);
188+
189+
const remoteData = useMemo(() => {
190+
return removeTypenames(data?.invitationLetterDocument.dynamicDocument);
191+
}, [data]);
192+
193+
return remoteData;
140194
};
141195

142196
const useSaveRemoteData = (): [(newData) => void, boolean, boolean] => {
@@ -160,30 +214,21 @@ const useSaveRemoteData = (): [(newData) => void, boolean, boolean] => {
160214
};
161215

162216
export const LocalStateProvider = ({ children }) => {
163-
const [localData, dispatch] = useReducer(reducer, null);
164-
const remoteData = useLoadRemoteData();
217+
const [localData, dispatch] = useReducer<State, any>(reducer, null);
218+
const remoteData = useLoadRemoteData(dispatch);
165219
const [saveChanges, isSaving, saveFailed] = useSaveRemoteData();
166220

167-
useEffect(() => {
168-
if (!remoteData) {
169-
return;
170-
}
171-
172-
dispatch({ type: ActionType.LoadData, payload: remoteData });
173-
}, [remoteData]);
221+
const isDirty = !equal(remoteData, localData);
174222

175223
return (
176224
<LocalStateContext.Provider
177225
value={{
178-
localData,
179-
saveChanges: () => {
180-
saveChanges(localData);
181-
},
226+
localData: localData || remoteData,
227+
isDirty,
228+
saveChanges: () => saveChanges(localData),
182229
isSaving,
183230
saveFailed,
184-
addPage: () => {
185-
dispatch({ type: ActionType.AddPage });
186-
},
231+
addPage: () => dispatch({ type: ActionType.AddPage }),
187232
setContent: (pageId, content) => {
188233
dispatch({
189234
type: ActionType.SetContent,

backend/custom_admin/src/components/invitation-letter-document-builder/menu-bar.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
AlignLeft,
77
AlignRight,
88
Bold,
9-
Braces,
109
Italic,
1110
} from "lucide-react";
1211

@@ -68,17 +67,6 @@ export const MenuBar = ({ editor }) => {
6867
>
6968
<AlignJustify size={16} />
7069
</Toolbar.Button>
71-
72-
<Toolbar.Separator className="w-px bg-gray-300 mx-1" />
73-
74-
<Toolbar.Button
75-
onClick={() => editor.chain().focus().setTextAlign("justify").run()}
76-
className={clsx("p-2 rounded hover:bg-gray-200", {
77-
"bg-gray-200": editor.isActive({ textAlign: "justify" }),
78-
})}
79-
>
80-
<Braces size={16} />
81-
</Toolbar.Button>
8270
</Toolbar.Root>
8371
);
8472
};

backend/custom_admin/src/components/invitation-letter-document-builder/root.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Flex, Spinner } from "@radix-ui/themes";
2+
import { Suspense } from "react";
13
import { Base } from "../shared/base";
24
import { DjangoAdminLayout } from "../shared/django-admin-layout";
35
import { InvitationLetterBuilder } from "./builder";
@@ -7,9 +9,25 @@ export const InvitationLetterDocumentBuilderRoot = () => {
79
return (
810
<Base>
911
<DjangoAdminLayout>
10-
<LocalStateProvider>
11-
<InvitationLetterBuilder />
12-
</LocalStateProvider>
12+
<Suspense
13+
fallback={
14+
<Flex
15+
align="center"
16+
justify="center"
17+
width="100%"
18+
height="100%"
19+
position="absolute"
20+
top="0"
21+
left="0"
22+
>
23+
<Spinner size="3" />
24+
</Flex>
25+
}
26+
>
27+
<LocalStateProvider>
28+
<InvitationLetterBuilder />
29+
</LocalStateProvider>
30+
</Suspense>
1331
</DjangoAdminLayout>
1432
</Base>
1533
);

0 commit comments

Comments
 (0)