Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:增加同步远程功能 #15

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ REACT_APP_DEV_ENABLE_SW=
REACT_APP_DEV_DISABLE_LIVE_RELOAD=

FAST_REFRESH=false

REACT_APP_WEBDAV_REMOTE_URL=http://localhost:4000/
2 changes: 2 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","
REACT_APP_GOOGLE_ANALYTICS_ID=UA-387204-13

REACT_APP_PLUS_APP=https://app.excalidraw.com

REACT_APP_WEBDAV_REMOTE_URL=http://localhost:4000/
83 changes: 83 additions & 0 deletions cloudstorge/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const express = require("express");
const fs = require("fs");
const path = require("path");
const basicAuth = require("express-basic-auth");

const app = express();

// Username and password for authentication
const AUTH_USERNAME = "draw";
const AUTH_PASSWORD = "draw123";

// Basic authentication middleware
const authMiddleware = basicAuth({
users: { [AUTH_USERNAME]: AUTH_PASSWORD },
challenge: true,
unauthorizedResponse: "Unauthorized",
});

app.use(authMiddleware);

// Custom middleware for logging request information
app.use((req, res, next) => {
const now = new Date();
console.log(`[${now.toLocaleString()}] ${req.method} ${req.url}`);
next();
});

// Serve static files from a directory
app.use(
"/draw",
authMiddleware,
express.static(path.join(__dirname, "public")),
);

// Enable CORS
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*"); // Replace * with your allowed origins
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
next();
});

app.options("*", (req, res) => {
res.sendStatus(200);
});

app.put("*", express.text(), (req, res) => {
const { url, body } = req;
const filePath = path.join(__dirname, url);

// Check if the directory exists, create if not
const directoryPath = path.dirname(filePath);
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath, { recursive: true });
}

fs.writeFile(filePath, body, "utf8", (err) => {
if (err) {
res.status(500).send("Error writing to file");
} else {
res.status(201).send("File written successfully");
}
});
});

app.get("*", (req, res) => {
const { url } = req;
const filePath = path.join(__dirname, url);

fs.readFile(filePath, "utf8", (err, data) => {
if (err) {
res.status(404).send("File not found");
} else {
res.setHeader("Content-Type", "application/octet-stream");
res.status(200).send(data);
}
});
});

const PORT = 4000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
15 changes: 15 additions & 0 deletions cloudstorge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "webdav",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"express-basic-auth": "^1.2.1"
}
}
1 change: 1 addition & 0 deletions cloudstorge/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world!!
6 changes: 4 additions & 2 deletions src/components/LayerUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,16 @@ const DefaultMainMenu: React.FC<{
{UIOptions.canvasActions.saveAsImage && (
<MainMenu.DefaultItems.SaveAsImage />
)}
<MainMenu.DefaultItems.Help />
{/* <MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.Group title="Excalidraw links">
<MainMenu.DefaultItems.Socials />
</MainMenu.Group>
<MainMenu.Separator />
<MainMenu.Separator /> */}
{/* <MainMenu.DefaultItems.ToggleTheme />
<MainMenu.DefaultItems.ToggleTheme />
<MainMenu.DefaultItems.ToggleTheme /> */}
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
);
Expand Down
5 changes: 5 additions & 0 deletions src/excalidraw-app/components/AppMainMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
// import { PlusPromoIcon } from "../../components/icons";
import { MainMenu } from "../../packages/excalidraw/index";
import { LanguageList } from "./LanguageList";
import { UploadLocalData } from "./LanguageList";

export const AppMainMenu: React.FC<{
setCollabDialogShown: (toggle: boolean) => any;
Expand Down Expand Up @@ -37,6 +38,10 @@ export const AppMainMenu: React.FC<{
<LanguageList style={{ width: "100%" }} />
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.ChangeCanvasBackground />
<MainMenu.Separator />
<MainMenu.ItemCustom>
<UploadLocalData style={{ width: "100%" }} />
</MainMenu.ItemCustom>
</MainMenu>
);
});
181 changes: 180 additions & 1 deletion src/excalidraw-app/components/LanguageList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useAtom } from "jotai";
import React from "react";
import React, { useState } from "react";
import { langCodeAtom } from "..";
import * as i18n from "../../i18n";
import { languages } from "../../i18n";
import "../index.scss"; // 引入样式文件

export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
const [langCode, setLangCode] = useAtom(langCodeAtom);
Expand All @@ -26,3 +27,181 @@ export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
</select>
);
};

export const UploadLocalData = ({ style }: { style?: React.CSSProperties }) => {
const [inputValue, setInputValue] = useState("");

const fetchData = async (data: any, path: any = inputValue) => {
const headers = new Headers();

const requestOptions: RequestInit = {
method: "PUT",
headers,
body: data, // Replace with your data to send
};

try {
await fetch(
`${
process.env.REACT_APP_SYSTEM_WEBDAV_REMOTE_URL
? process.env.REACT_APP_SYSTEM_WEBDAV_REMOTE_URL
: process.env.REACT_APP_WEBDAV_REMOTE_URL
}${path}`,
requestOptions,
);
alert("上传成功");
// Process the response as needed
} catch (error) {
// Handle error
console.error("Error fetching data:", error);
}
};

const getData = async (path: any) => {
if (
window.location.search &&
window.location.search.startsWith("?sharePath=")
) {
path = window.location.search.slice(7);
}
console.log(inputValue, path);
const sharePath = typeof path === "string";
const storgePath = sharePath ? path : inputValue;

if (!storgePath) {
alert("请输入远程路径!!!");
return;
}

const headers = new Headers();
// headers.append('Authorization', 'Basic ' + window.btoa(inputValue)); // Replace with your actual token

const requestOptions: RequestInit = {
method: "GET",
headers,
};

try {
const response = await fetch(
`${
process.env.REACT_APP_SYSTEM_WEBDAV_REMOTE_URL
? process.env.REACT_APP_SYSTEM_WEBDAV_REMOTE_URL
: process.env.REACT_APP_WEBDAV_REMOTE_URL
}${storgePath}`,
requestOptions,
);
console.log(response);
const responseBody = await response.text(); // Convert response body to text
console.log(responseBody); // Process the response body as needed
const drawData = JSON.parse(responseBody);
// backup
const currentLocalData = window.localStorage;
// Filter and store backup data
const filteredData: { [key: string]: any } = {};
for (const key in currentLocalData) {
if (currentLocalData.hasOwnProperty(key) && key.startsWith("backup")) {
// 过滤掉以 "backup" 开头的键
continue;
}
filteredData[key] = currentLocalData[key];
}

const backupKeys = Object.keys(currentLocalData).filter((key) =>
key.startsWith("backup"),
);
// 删除之前备份数据
for (const key of backupKeys) {
currentLocalData.removeItem(key);
}
// 存储新的 "backup" 数据
const newBackupKey = `backup${new Date().getTime()}`;
currentLocalData.setItem(newBackupKey, JSON.stringify(filteredData));
// 上传到远程
fetchData(JSON.stringify(filteredData), `backup/${newBackupKey}`);
console.log("Backup saved:", newBackupKey);
if (sharePath) {
// 分享单文件 追加
console.log(currentLocalData.excalidraw_container_list);
const currentDrawkey = Object.keys(drawData)[0];
const excalidraw_container_list =
currentLocalData.excalidraw_container_list;
let excalidraw_container = new Set();
if (excalidraw_container_list) {
console.log(excalidraw_container_list);

excalidraw_container = new Set(JSON.parse(excalidraw_container_list));
}
excalidraw_container.add(currentDrawkey);
console.log(typeof excalidraw_container, excalidraw_container);
currentLocalData.setItem(
"excalidraw_container_list",
JSON.stringify(Array.from(excalidraw_container)),
);
} else {
window.localStorage.clear();
}
for (const item in drawData) {
window.localStorage.setItem(item, drawData[item]);
}
window.localStorage.setItem(
"excalidraw_container_name",
"default_canvas",
);
alert(
"本地数据已备份远程路径" +
`backup/${newBackupKey}` +
",同步远程数据成功,请切换画布查看效果!!",
);

// Process the response as needed
} catch (error) {
// Handle error
console.error("Error fetching data:", error);
}
};

const handleUploadClick = () => {
if (!inputValue) {
alert("请输入远程路径!!!");
return;
}
const data = window.localStorage;
fetchData(JSON.stringify(data)); // Call the fetchData function to make the request
};

const handleCurUploadClick = () => {
if (!inputValue) {
alert("请输入远程路径!!!");
return;
}
const data = window.localStorage;
const excalidraw_container_name = data.excalidraw_container_name;
const excalidraw_container_data = data[excalidraw_container_name];
const reqData: { [key: string]: any } = {};
reqData[excalidraw_container_name] = excalidraw_container_data;
fetchData(JSON.stringify(reqData));
};
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(event.target.value);
};

return (
<div>
<button onClick={handleUploadClick} className="button">
上传
</button>
<button onClick={getData} className="button">
同步
</button>
<button onClick={handleCurUploadClick} className="button">
分享
</button>
<input
placeholder="远程路径"
value={inputValue}
onChange={handleInputChange}
className="input"
/>
</div>
);
};
14 changes: 14 additions & 0 deletions src/excalidraw-app/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,17 @@
}
}
}

.button {
margin-right: 0.2em;
border-radius: 0.5em;
border: none;
background-color: #d1d4d1;
}

.input {
margin-top: 0.4em;
padding: 0.5em;
border-radius: 0.5em;
border: 0.1em solid #ccc;
}
Loading