Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/api/collaboration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios from "axios";

// Using jsonblob.com as a simple JSON store for the MVP.
// This allows testing the collaboration feature without setting up a backend.
// In a real deployment, replace API_URL with your own backend service (e.g., Supabase, Firebase functions).
const API_URL = "https://jsonblob.com/api/jsonBlob";

/**
* Creates a new session with the initial diagram data.
* @param {Object} data - The diagram JSON object.
* @returns {Promise<string>} - The session ID.
*/
export async function createSession(data) {
const response = await axios.post(API_URL, data);
// JsonBlob returns the Location header with the full URL to the blob
// e.g. https://jsonblob.com/api/jsonBlob/11bea...
const location = response.headers["location"] || response.headers["Location"];
if (!location) {
throw new Error("Failed to retrieve session ID from server.");
}
const id = location.split("/").pop();
return id;
}

/**
* Retrieves the diagram data for a given session ID.
* @param {string} id - The session ID.
* @returns {Promise<Object>} - The diagram JSON object.
*/
export async function getSession(id) {
const response = await axios.get(`${API_URL}/${id}`);
return response.data;
}

/**
* Updates an existing session with new diagram data.
* @param {string} id - The session ID.
* @param {Object} data - The diagram JSON object.
* @returns {Promise<void>}
*/
export async function updateSession(id, data) {
await axios.put(`${API_URL}/${id}`, data);
}
106 changes: 60 additions & 46 deletions src/components/EditorHeader/ControlPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IconRedo,
IconEdit,
IconShareStroked,
IconUserGroup,
} from "@douyinfe/semi-icons";
import { Link, useNavigate } from "react-router-dom";
import icon from "../../assets/icon_dark_64.png";
Expand Down Expand Up @@ -239,9 +240,9 @@ export default function ControlPanel({
indices: table.indices.map((index) =>
index.id === a.iid
? {
...index,
...a.undo,
}
...index,
...a.undo,
}
: index,
),
});
Expand Down Expand Up @@ -420,9 +421,9 @@ export default function ControlPanel({
indices: table.indices.map((index) =>
index.id === a.iid
? {
...index,
...a.redo,
}
...index,
...a.redo,
}
: index,
),
});
Expand Down Expand Up @@ -786,19 +787,19 @@ export default function ControlPanel({
t.id
? t
: {
...t,
id: nanoid(),
fields: t.fields.map((f) =>
f.id ? f : { ...f, id: nanoid() },
),
},
...t,
id: nanoid(),
fields: t.fields.map((f) =>
f.id ? f : { ...f, id: nanoid() },
),
},
),
);
}
if (databases[diagram.database].hasEnums) {
setEnums(
diagram.enums.map((e) => (!e.id ? { ...e, id: nanoid() } : e)) ??
[],
[],
);
}
window.name = `d ${diagram.id}`;
Expand Down Expand Up @@ -831,31 +832,31 @@ export default function ControlPanel({
children: [
...(recentlyOpenedDiagrams && recentlyOpenedDiagrams.length > 0
? [
...recentlyOpenedDiagrams.map((diagram) => ({
name: diagram.name,
label: DateTime.fromJSDate(new Date(diagram.lastModified))
.setLocale(i18n.language)
.toRelative(),
function: async () => {
await loadDiagram(diagram.id);
save();
},
})),
{ divider: true },
{
name: t("see_all"),
function: () => open(),
...recentlyOpenedDiagrams.map((diagram) => ({
name: diagram.name,
label: DateTime.fromJSDate(new Date(diagram.lastModified))
.setLocale(i18n.language)
.toRelative(),
function: async () => {
await loadDiagram(diagram.id);
save();
},
]
})),
{ divider: true },
{
name: t("see_all"),
function: () => open(),
},
]
: [
{
name: t("no_saved_diagrams"),
disabled: true,
},
]),
{
name: t("no_saved_diagrams"),
disabled: true,
},
]),
],

function: () => {},
function: () => { },
},
save: {
function: save,
Expand Down Expand Up @@ -1271,7 +1272,7 @@ export default function ControlPanel({
},
},
],
function: () => {},
function: () => { },
},
exit: {
function: () => {
Expand Down Expand Up @@ -1503,7 +1504,7 @@ export default function ControlPanel({
function: () => setSettings((prev) => ({ ...prev, mode: "dark" })),
},
],
function: () => {},
function: () => { },
},
zoom_in: {
function: zoomIn,
Expand Down Expand Up @@ -1627,15 +1628,28 @@ export default function ControlPanel({
>
{header()}
{window.name.split(" ")[0] !== "t" && (
<Button
type="primary"
className="!text-base me-2 !pe-6 !ps-5 !py-[18px] !rounded-md"
size="default"
icon={<IconShareStroked />}
onClick={() => setModal(MODAL.SHARE)}
>
{t("share")}
</Button>
<>
<Button
theme="light"
type="tertiary"
className="!text-base me-2 !pe-6 !ps-5 !py-[18px] !rounded-md"
size="default"
icon={<IconUserGroup />}
onClick={() => setModal(MODAL.COLLABORATION)}
style={{ marginRight: 8 }}
>
Collab
</Button>
<Button
type="primary"
className="!text-base me-2 !pe-6 !ps-5 !py-[18px] !rounded-md"
size="default"
icon={<IconShareStroked />}
onClick={() => setModal(MODAL.SHARE)}
>
{t("share")}
</Button>
</>
)}
</div>
)}
Expand Down Expand Up @@ -2057,7 +2071,7 @@ export default function ControlPanel({
type="light"
prefixIcon={
saveState === State.LOADING ||
saveState === State.SAVING ? (
saveState === State.SAVING ? (
<Spin size="small" />
) : null
}
Expand Down
Loading
Loading