commit
cc1a4d2b5f
@ -0,0 +1,9 @@
|
||||
/uploads
|
||||
/main
|
||||
/*.exe
|
||||
*.log
|
||||
/go-demo-*
|
||||
/FileSync*
|
||||
node_modules
|
||||
/build
|
||||
*.syso
|
@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": ["error", { "vars": "local", "args": "none", "ignoreRestSiblings": true }],
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/prop-types": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.{ts,tsx}"],
|
||||
"rules": {
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
}
|
||||
],
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/src/images/synk.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>同步传</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script src="//at.alicdn.com/t/font_2846367_4w90lpzz304.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build --base ./",
|
||||
"lint": "eslint --ext .jsx --ext .js --ext .ts --ext .tsx ./src",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.22.0",
|
||||
"classnames": "2.3.1",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.21",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-is": "17.0.2",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"styled-components": "5.3.1",
|
||||
"swr": "1.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.24",
|
||||
"@types/react-dom": "17.0.9",
|
||||
"@types/styled-components": "^5.1.14",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vitejs/plugin-react": "^1.0.2",
|
||||
"@vitejs/plugin-react-refresh": "1.3.6",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-react": "^7.26.1",
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "2.6.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import styled from "styled-components";
|
||||
export const Center = styled.div`
|
||||
display: flex;
|
||||
flex-direction: ${({ virtical }) => virtical ? 'column' : 'row'};
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
@ -0,0 +1,55 @@
|
||||
import { createContext } from "react";
|
||||
import { unmountComponentAtNode, render, createPortal } from "react-dom";
|
||||
import styled from "styled-components";
|
||||
import { AppContext } from "../shared/app_context";
|
||||
|
||||
const DialogOverlay = styled.div`
|
||||
position: fixed; z-index: 10; background: rgba(0,0,0,0.5); width: 100%;
|
||||
height: 100%; left: 0; top: 0;
|
||||
`;
|
||||
const DialogContent = styled.div`
|
||||
position: fixed; z-index: 11; min-width: 120px; min-height: 40px;
|
||||
max-width: 100%; max-height: 100%; background: white; top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 8px;
|
||||
`;
|
||||
|
||||
export const Dialog = ({ container, onClickOverlay, children }) => {
|
||||
return createPortal(
|
||||
<>
|
||||
<DialogOverlay onClick={onClickOverlay} />
|
||||
<DialogContent>{children}</DialogContent>
|
||||
</>,
|
||||
container ?? document.body
|
||||
);
|
||||
};
|
||||
|
||||
let lastDiv = null
|
||||
|
||||
const close = (div) => {
|
||||
if (!div) return
|
||||
unmountComponentAtNode(div);
|
||||
div.remove();
|
||||
lastDiv = null
|
||||
};
|
||||
export const createDialog = (content, options = {}) => {
|
||||
close(lastDiv)
|
||||
const { closeOnClickOverlay, context } = options;
|
||||
const div = lastDiv = document.createElement("div");
|
||||
div.className = "tempApp";
|
||||
document.body.append(div);
|
||||
const onClickOverlay = () => {
|
||||
if (closeOnClickOverlay) {
|
||||
close(div);
|
||||
}
|
||||
};
|
||||
render(
|
||||
<AppContext.Provider value={context}>
|
||||
<Dialog container={div} onClickOverlay={onClickOverlay}>
|
||||
{content}
|
||||
</Dialog>
|
||||
</AppContext.Provider>,
|
||||
div
|
||||
);
|
||||
return () => close(div);
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { Center } from "./center";
|
||||
import styled from "styled-components";
|
||||
|
||||
const _Loading = styled(Center)`
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
> svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Loading = ({ children, className }) => {
|
||||
return (
|
||||
<_Loading className={className}>
|
||||
<svg className="spin">
|
||||
<use xlinkHref="#icon-loading" />
|
||||
</svg>
|
||||
<p>{children}</p>
|
||||
</_Loading>
|
||||
);
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
import styled from "styled-components"
|
||||
export const Space = styled.div`
|
||||
height: ${({ x2, x3 }) => x2 ? 16 * 2 : x3 ? 16 * 3 : 16}px;
|
||||
`
|
@ -0,0 +1,7 @@
|
||||
import { useLocation } from "react-router";
|
||||
import qs from 'query-string'
|
||||
export const useQuery = () => {
|
||||
const location = useLocation();
|
||||
const parsed = qs.parse(location.search)
|
||||
return parsed
|
||||
}
|
After Width: | Height: | Size: 448 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,9 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
let clientId = localStorage.getItem('clientId')
|
||||
if (clientId?.length !== 36) {
|
||||
clientId = uuidv4();
|
||||
localStorage.setItem('clientId', clientId)
|
||||
}
|
||||
|
||||
export { clientId }
|
@ -0,0 +1,4 @@
|
||||
import './client_id'
|
||||
export const init = () => {
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Router, Route, Switch, Redirect } from "react-router";
|
||||
import { Home } from "./pages/home";
|
||||
import { Downloads } from "./pages/downloads";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { GlobalStyle } from './shared/global_style'
|
||||
import { history } from "./shared/history";
|
||||
import { init } from "./initializers";
|
||||
import { getWsClient } from "./shared/ws_client";
|
||||
import { clientId } from "./initializers/client_id";
|
||||
import { createDialog } from "./components/dialog";
|
||||
import { showUploadFileSuccessDialog, showUploadTextSuccessDialog } from "./pages/home/components";
|
||||
import { http } from "./shared/http";
|
||||
import _ from "lodash";
|
||||
import { AppContext } from "./shared/app_context";
|
||||
|
||||
|
||||
const theme = {
|
||||
borderColor: "#333",
|
||||
highlightColor: "#f5b70d",
|
||||
};
|
||||
|
||||
const Main = () => {
|
||||
init()
|
||||
const addressesRef = useRef(null);
|
||||
const context = { addressesRef }
|
||||
useEffect(async () => {
|
||||
const {
|
||||
data: { addresses },
|
||||
} = await http
|
||||
.get("/api/v1/addresses")
|
||||
.catch((e) => Promise.reject(e));
|
||||
addressesRef.current = _.uniq(addresses.concat("127.0.0.1"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getWsClient().then(c => {
|
||||
c.onMessage(data => {
|
||||
const { url, type } = data
|
||||
if (data.clientId !== clientId) {
|
||||
const content = (addr) => addr && `http://${addr}:27149/static/downloads?type=${type}&url=${encodeURIComponent(`http://${addr}:27149${url}`)}`
|
||||
if (type === 'text') {
|
||||
showUploadTextSuccessDialog({ context, content });
|
||||
} else {
|
||||
showUploadFileSuccessDialog({ context, content });
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
return <ThemeProvider theme={theme}>
|
||||
<GlobalStyle />
|
||||
<AppContext.Provider value={context}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Redirect exact from="/" to="/message" />
|
||||
<Route exact path="/downloads">
|
||||
<Downloads />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route path="*">
|
||||
<div>404</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Main />
|
||||
</React.StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
@ -0,0 +1,26 @@
|
||||
import { Switch, Route } from "react-router";
|
||||
import { Header, Layout } from "./home/components";
|
||||
import { UploadTextForm } from "./home/upload_text_form";
|
||||
import { UploadFileForm } from "./home/upload_file_form";
|
||||
import { nav } from "./home/nav";
|
||||
import { UploadScreenshotForm } from "./home/upload_screenshot_form";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<Layout>
|
||||
<Header>同步传</Header>
|
||||
{nav}
|
||||
<Switch>
|
||||
<Route exact path="/message">
|
||||
<UploadTextForm />
|
||||
</Route>
|
||||
<Route exact path="/file">
|
||||
<UploadFileForm />
|
||||
</Route>
|
||||
<Route exact path="/screenshot">
|
||||
<UploadScreenshotForm />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Layout>
|
||||
);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
const Nav = styled.nav`
|
||||
text-align: center;
|
||||
> ul {
|
||||
display: flex; border-top: 1px solid ${({ theme }) => theme.borderColor};
|
||||
border-left: 1px solid ${({ theme }) => theme.borderColor};
|
||||
> li { flex-grow: 1; border-bottom: 1px solid #333;
|
||||
border-right: 1px solid ${({ theme }) => theme.borderColor};
|
||||
> a { display: block; padding: 8px 0;
|
||||
&.selected{ background: ${({ theme }) => theme.highlightColor} }
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const nav = (
|
||||
<Nav>
|
||||
<ul>
|
||||
<li>
|
||||
<NavLink to="/message" activeClassName="selected">
|
||||
传消息
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/file" activeClassName="selected">
|
||||
传文件
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/screenshot" activeClassName="selected">
|
||||
传截图
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Nav>
|
||||
);
|
@ -0,0 +1,79 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Form,
|
||||
showUploadingDialog,
|
||||
showUploadFileSuccessDialog,
|
||||
uploadFile,
|
||||
} from "../../pages/home/components";
|
||||
import { AppContext } from "../../shared/app_context";
|
||||
|
||||
export const UploadFileForm = () => {
|
||||
const context = useContext(AppContext);
|
||||
const [boxClass, setBoxClass] = useState("default");
|
||||
const onDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
setBoxClass("dragging");
|
||||
};
|
||||
const onDragLeave = (e) => {
|
||||
setBoxClass("default");
|
||||
};
|
||||
const onDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer?.items?.[0]?.getAsFile();
|
||||
if (!file) return;
|
||||
const type = file.type || "unknown";
|
||||
showUploadingDialog();
|
||||
const { data: { url } } = await uploadFile(file);
|
||||
showUploadFileSuccessDialog({
|
||||
context,
|
||||
content: (addr) =>
|
||||
addr &&
|
||||
`http://${addr}:27149/static/downloads?type=${type}&url=${encodeURIComponent(
|
||||
`http://${addr}:27149${url}`
|
||||
)}`,
|
||||
});
|
||||
};
|
||||
const onChange = async (e) => {
|
||||
const file = e.target?.files?.[0];
|
||||
if (!file) return;
|
||||
const type = file.type || "unknown";
|
||||
showUploadingDialog();
|
||||
const { data: { url } } = await uploadFile(file);
|
||||
showUploadFileSuccessDialog({
|
||||
context,
|
||||
content: (addr) =>
|
||||
addr &&
|
||||
`http://${addr}:27149/static/downloads?type=${type}&url=${encodeURIComponent(
|
||||
`http://${addr}:27149${url}`
|
||||
)}`,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Form className="uploadForm">
|
||||
<div className="row">
|
||||
<Box
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
className={boxClass}
|
||||
>
|
||||
<FileInput type="file" value="" onChange={onChange} />
|
||||
<p>点击打开文件 或 拖拽文件至此</p>
|
||||
</Box>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
const Box = styled.div`
|
||||
&.dragging {
|
||||
border-color: ${({ theme }) => theme.highlightColor};
|
||||
color: ${({ theme }) => theme.highlightColor};
|
||||
}
|
||||
min-height: 160px; border: 2px dashed ${({ theme }) => theme.borderColor};
|
||||
position: relative; overflow: hidden; display: flex; justify-content: center; align-items: center; border-radius: 8px;
|
||||
`;
|
||||
const FileInput = styled.input`
|
||||
position: absolute; right: 0; top: 0; width: 100%; height: 100%;
|
||||
opacity: 0; cursor: pointer;
|
||||
`;
|
@ -0,0 +1,66 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Form,
|
||||
showUploadingDialog,
|
||||
showUploadFileSuccessDialog,
|
||||
uploadFile,
|
||||
} from "../../pages/home/components";
|
||||
import { AppContext } from "../../shared/app_context";
|
||||
export const UploadScreenshotForm = () => {
|
||||
const context = useContext(AppContext);
|
||||
const _uploadFile = async (file) => {
|
||||
if (!file) return;
|
||||
const type = file.type || "unknown";
|
||||
showUploadingDialog();
|
||||
const { data: { url } } = await uploadFile(file);
|
||||
showUploadFileSuccessDialog({
|
||||
context,
|
||||
content: (addr) =>
|
||||
addr &&
|
||||
`http://${addr}:27149/static/downloads?type=${type}&url=${encodeURIComponent(
|
||||
`http://${addr}:27149${url}`
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
const onPaste = (e) => {
|
||||
const { items: [item] } = e.clipboardData;
|
||||
_uploadFile(item?.getAsFile())
|
||||
};
|
||||
useEffect(() => {
|
||||
window.addEventListener("paste", onPaste);
|
||||
return () => {
|
||||
window.removeEventListener("paste", onPaste);
|
||||
};
|
||||
}, []);
|
||||
const onChange = async (e) => {
|
||||
_uploadFile(e.target?.files?.[0])
|
||||
};
|
||||
return (
|
||||
<Form className="uploadForm">
|
||||
<div className="row">
|
||||
<Box>
|
||||
<FileInput
|
||||
type="file"
|
||||
value=""
|
||||
onChange={onChange}
|
||||
accept="image/*;capture=camera"
|
||||
/>
|
||||
<p>点击选择图片 或 直接粘贴图片</p>
|
||||
</Box>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
const Box = styled.div`
|
||||
&.dragging {
|
||||
border-color: ${({ theme }) => theme.highlightColor};
|
||||
color: ${({ theme }) => theme.highlightColor};
|
||||
}
|
||||
min-height: 160px; border: 2px dashed ${({ theme }) => theme.borderColor};
|
||||
position: relative; overflow: hidden; display: flex; justify-content: center; align-items: center; border-radius: 8px;
|
||||
`;
|
||||
const FileInput = styled.input`
|
||||
position: absolute; right: 0; top: 0; width: 100%; height: 100%;
|
||||
opacity: 0; cursor: pointer;
|
||||
`;
|
@ -0,0 +1,37 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import {
|
||||
BigTextarea,
|
||||
Button,
|
||||
Form,
|
||||
showUploadingDialog,
|
||||
showUploadTextSuccessDialog,
|
||||
uploadText,
|
||||
} from "../../pages/home/components";
|
||||
import { AppContext } from "../../shared/app_context";
|
||||
import { Center } from "../../components/center";
|
||||
|
||||
export const UploadTextForm = () => {
|
||||
const context = useContext(AppContext);
|
||||
const [text, setText] = useState("");
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
showUploadingDialog();
|
||||
const { data: { url } } = await uploadText(text)
|
||||
showUploadTextSuccessDialog({
|
||||
context, content: (addr) => addr && `http://${addr}:27149/static/downloads?type=text&url=http://${addr + ":27149" + encodeURIComponent(url)}`
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Form className="uploadForm" onSubmit={onSubmit}>
|
||||
<div className="row">
|
||||
<BigTextarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Center className="row">
|
||||
<Button type="submit">上传</Button>
|
||||
</Center>
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -0,0 +1,3 @@
|
||||
import React from 'react'
|
||||
export const AppContext = React.createContext({ addressesRef: null })
|
||||
|
@ -0,0 +1,28 @@
|
||||
import { createGlobalStyle, keyframes } from "styled-components";
|
||||
const spin = keyframes`
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100%{
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
export const GlobalStyle = createGlobalStyle`
|
||||
* { box-sizing: border-box; padding: 0; margin: 0; }
|
||||
*::before, *::after {box-sizing: border-box;}
|
||||
body {
|
||||
font-size: 16px;
|
||||
font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;
|
||||
}
|
||||
a {text-decoration: none; color: inherit;}
|
||||
img {max-width: 100%; max-height: 100%; }
|
||||
input, button {font: inherit;}
|
||||
ul, ol {list-style: none; }
|
||||
img{vertical-align: middle;}
|
||||
:focus{ outline: none; }
|
||||
|
||||
// helpers
|
||||
.spin {
|
||||
animation: ${spin} 2s linear infinite;
|
||||
}
|
||||
`;
|
@ -0,0 +1,2 @@
|
||||
import { createBrowserHistory } from "history";
|
||||
export const history = createBrowserHistory({ basename: "/static/" });
|
@ -0,0 +1,4 @@
|
||||
import axios from "axios";
|
||||
export const http = axios.create({
|
||||
timeout: 5000
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
export const prefetch = (src) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const picture = new Image()
|
||||
picture.onload = () => {
|
||||
resolve(picture)
|
||||
}
|
||||
picture.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
picture.src = src
|
||||
})
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
const url = `ws://${window.location.hostname}:27149/ws`;
|
||||
const wsClient = new WebSocket(url);
|
||||
|
||||
class WsClient {
|
||||
constructor(client) {
|
||||
this.client = client
|
||||
}
|
||||
send(data) {
|
||||
this.client.send(JSON.stringify(data))
|
||||
}
|
||||
onMessage(fn) {
|
||||
this.client.onmessage = ({ data }) => {
|
||||
fn(JSON.parse(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
wsClient.onopen = () => {
|
||||
resolve(new WsClient(wsClient))
|
||||
}
|
||||
setTimeout(() => {
|
||||
reject(new Error('get ws connection timeout'))
|
||||
}, 10000)
|
||||
})
|
||||
|
||||
export const getWsClient = () => promise
|
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:27149/',
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
module github.com/zggsong/FileSync
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/gin-gonic/gin v1.7.7
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/golang/protobuf v1.3.3 // indirect
|
||||
github.com/json-iterator/go v1.1.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
|
||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
)
|
@ -0,0 +1,54 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist/*
|
||||
var FS embed.FS
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
router := gin.Default()
|
||||
|
||||
staticFiles, _ := fs.Sub(FS, "frontend/dist")
|
||||
router.StaticFS("/static", http.FS(staticFiles))
|
||||
|
||||
}()
|
||||
|
||||
}
|
Loading…
Reference in new issue