diff --git a/.travis.yml b/.travis.yml
index 17a41682..65a02d2c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -34,7 +34,7 @@ jobs:
- stage: Deploy
if: branch = master AND type = pull_request
before_install:
- - openssl aes-256-cbc -K $encrypted_278d2d9eb060_key -iv $encrypted_278d2d9eb060_iv -in env.tar.enc -out env.tar -d
+ - openssl aes-256-cbc -K $encrypted_0395e9fbd9ff_key -iv $encrypted_0395e9fbd9ff_iv -in env.tar.enc -out env.tar -d
- tar xvf env.tar
install:
- sudo apt-get install sshpass
diff --git a/cocode/dev.env b/cocode/dev.env
index 26366660..da0b0cf0 100644
--- a/cocode/dev.env
+++ b/cocode/dev.env
@@ -1,4 +1,5 @@
SKIP_PREFLIGHT_CHECK=true
+
DEV_API_SERVER_IP=
PROD_API_SERVER_IP=
@@ -7,3 +8,9 @@ DEV_DEPENDENCY_SERVER_IP=
PROD_COCONUT_SERVER_IP=
DEV_COCONUT_SERVER_IP=
+
+PROD_LIVE_SERVER_IP=
+DEV_LIVE_SERVER_IP=
+
+PROD_COCODE_SERVER_IP=
+DEV_COCODE_SERVER_IP=
\ No newline at end of file
diff --git a/cocode/package.json b/cocode/package.json
index 38a7e5bc..426ddd20 100644
--- a/cocode/package.json
+++ b/cocode/package.json
@@ -55,6 +55,7 @@
"react-router-dom": "^5.1.2",
"react-scripts": "^3.2.0",
"react-style-proptype": "^3.2.2",
+ "socket.io-client": "^2.3.0",
"styled-components": "^4.4.1",
"webpack": "^4.41.2",
"worker-loader": "^2.0.0"
diff --git a/cocode/public/favicon-16x16.png b/cocode/public/favicon-16x16.png
new file mode 100644
index 00000000..38f6a74a
Binary files /dev/null and b/cocode/public/favicon-16x16.png differ
diff --git a/cocode/public/index.html b/cocode/public/index.html
index e0ab6b04..7d41b699 100644
--- a/cocode/public/index.html
+++ b/cocode/public/index.html
@@ -2,6 +2,12 @@
cocode! - the SaaS IDE
+
+
@@ -29,7 +28,13 @@ function App() {
-
+
+
+
+
+
+
+
diff --git a/cocode/src/actions/Live.js b/cocode/src/actions/Live.js
index e9ca1565..b04f9e52 100644
--- a/cocode/src/actions/Live.js
+++ b/cocode/src/actions/Live.js
@@ -1,31 +1,26 @@
import {
- FETCH_LIVE,
LIVE_ON,
LIVE_OFF,
LIVE_JOIN_USER,
- LIVE_LEFT_USER
+ LIVE_LEAVE_USER
} from './types';
-function fetchLiveActionCreator(payload) {
- return { type: FETCH_LIVE, payload };
-}
function liveOnActionCreator(payload) {
return { type: LIVE_ON, payload };
}
-function liveOffActionCreator(payload) {
- return { type: LIVE_OFF, payload };
+function liveOffActionCreator() {
+ return { type: LIVE_OFF };
}
function liveJoinUserActionCreator(payload) {
return { type: LIVE_JOIN_USER, payload };
}
-function liveLeftUserActionCreator(payload) {
- return { type: LIVE_LEFT_USER, payload };
+function liveLeaveUserActionCreator(payload) {
+ return { type: LIVE_LEAVE_USER, payload };
}
export {
- fetchLiveActionCreator,
liveOnActionCreator,
liveOffActionCreator,
liveJoinUserActionCreator,
- liveLeftUserActionCreator
+ liveLeaveUserActionCreator
};
diff --git a/cocode/src/actions/Project.js b/cocode/src/actions/Project.js
index 29d4ca36..0c84f278 100644
--- a/cocode/src/actions/Project.js
+++ b/cocode/src/actions/Project.js
@@ -1,6 +1,7 @@
import {
UPDATE_PROJECT_INFO,
UPDATE_CODE,
+ UPDATE_CODE_FROM_FILE_ID,
FETCH_PROJECT,
SELECT_FILE,
UPDATE_FILE_NAME,
@@ -23,6 +24,9 @@ function fetchProjectActionCreator(payload) {
function updateCodeActionCreator(payload) {
return { type: UPDATE_CODE, payload };
}
+function updateCodeFromFileIdActionCreator(payload) {
+ return { type: UPDATE_CODE_FROM_FILE_ID, payload };
+}
function selectFileActionCreator(payload) {
return { type: SELECT_FILE, payload };
@@ -59,6 +63,7 @@ function saveFileActionCreator(payload) {
export {
updateProjectInfoActionCreator,
updateCodeActionCreator,
+ updateCodeFromFileIdActionCreator,
fetchProjectActionCreator,
selectFileActionCreator,
updateFileNameActionCreator,
diff --git a/cocode/src/actions/types.js b/cocode/src/actions/types.js
index a88427d4..6532f671 100644
--- a/cocode/src/actions/types.js
+++ b/cocode/src/actions/types.js
@@ -7,6 +7,7 @@ const API_FAIL = 'API_FAILURE';
// Project
const UPDATE_PROJECT_INFO = 'updateProjectInfo';
const UPDATE_CODE = 'updateCode';
+const UPDATE_CODE_FROM_FILE_ID = 'updateCodeFromFileId';
const FETCH_PROJECT = 'fetchProject';
const SELECT_FILE = 'selectFile';
const CREATE_FILE = 'createFile';
@@ -23,11 +24,10 @@ const UPDATE_COCONUT_NAME = 'updateCoconutName';
const DELETE_COCONUT = 'deleteCoconut';
//Live
-const FETCH_LIVE = 'fetchLive';
const LIVE_ON = 'liveOn';
const LIVE_OFF = 'liveOff';
const LIVE_JOIN_USER = 'liveJoinUser';
-const LIVE_LEFT_USER = 'liveLeftUser';
+const LIVE_LEAVE_USER = 'liveLeaveUser';
export {
API_READY,
@@ -36,6 +36,7 @@ export {
API_FAIL,
UPDATE_PROJECT_INFO,
UPDATE_CODE,
+ UPDATE_CODE_FROM_FILE_ID,
FETCH_PROJECT,
SELECT_FILE,
UPDATE_FILE_NAME,
@@ -47,10 +48,9 @@ export {
FETCH_COCONUT,
UPDATE_COCONUT_NAME,
DELETE_COCONUT,
- FETCH_LIVE,
LIVE_ON,
LIVE_OFF,
LIVE_JOIN_USER,
- LIVE_LEFT_USER,
+ LIVE_LEAVE_USER,
SAVE_FILE
};
diff --git a/cocode/src/apis/Dependency.js b/cocode/src/apis/Dependency.js
deleted file mode 100644
index d1435fb0..00000000
--- a/cocode/src/apis/Dependency.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { DEPENDENCY } from 'config';
-
-function getModule(moduleName, moduleVersion) {
- return {
- url: `${DEPENDENCY.modules}`,
- method: 'post',
- data: {
- moduleName,
- moduleVersion
- }
- };
-}
-
-export { getModule };
diff --git a/cocode/src/components/Common/DropZone/index.js b/cocode/src/components/Common/DropZone/index.js
index f31fd37b..3a065c7a 100644
--- a/cocode/src/components/Common/DropZone/index.js
+++ b/cocode/src/components/Common/DropZone/index.js
@@ -26,7 +26,7 @@ function DropZone({ draggableComponentOverColor, ...props }) {
onDragLeave={handleDragLeave}
onDrop={handleDrop}
{...props}
- >
+ />
);
}
diff --git a/cocode/src/components/Common/LoginModalBody/index.js b/cocode/src/components/Common/LoginModalBody/index.js
index c086585e..b9ad3c54 100644
--- a/cocode/src/components/Common/LoginModalBody/index.js
+++ b/cocode/src/components/Common/LoginModalBody/index.js
@@ -6,7 +6,10 @@ import { API } from 'config';
import Github from './github.svg';
function LoginModalBody() {
- const handleClickLoginButton = () => (window.location.href = API.login);
+ const handleClickLoginButton = () => {
+ window.location.href = API.login;
+ localStorage.setItem('redirectURL', window.location.href);
+ };
return (
diff --git a/cocode/src/components/Common/Modal/style.js b/cocode/src/components/Common/Modal/style.js
index 5ed807dd..e5ecf616 100644
--- a/cocode/src/components/Common/Modal/style.js
+++ b/cocode/src/components/Common/Modal/style.js
@@ -2,6 +2,7 @@ import styled from 'styled-components';
const ModalBackGround = styled.div`
& {
+ z-index: 10;
position: fixed;
top: 0;
left: 0;
diff --git a/cocode/src/components/DashBoard/ProjectCard/index.js b/cocode/src/components/DashBoard/ProjectCard/index.js
index 61fca6ce..12e882db 100644
--- a/cocode/src/components/DashBoard/ProjectCard/index.js
+++ b/cocode/src/components/DashBoard/ProjectCard/index.js
@@ -15,7 +15,7 @@ import {
updateCoconutNameActionCreator,
deleteCoconutActionCreator
} from 'actions/Dashboard';
-import DashBoardContext from 'contexts/DashBoardContext';
+import { DashBoardContext } from 'contexts';
import useFetch from 'hooks/useFetch';
import {
updateCoconutsAPICreator,
diff --git a/cocode/src/components/DashBoard/ProjectCard/style.js b/cocode/src/components/DashBoard/ProjectCard/style.js
index 3deb81ad..445e5505 100644
--- a/cocode/src/components/DashBoard/ProjectCard/style.js
+++ b/cocode/src/components/DashBoard/ProjectCard/style.js
@@ -15,6 +15,7 @@ const ProjectArticle = styled.article`
background-color: ${PROJECT_CARD_THEME.cardBackgroundColor};
border-radius: 1rem;
+ cursor: pointer;
}
`;
diff --git a/cocode/src/components/Project/LiveUserProfile/index.js b/cocode/src/components/Live/LiveUsers/index.js
similarity index 79%
rename from cocode/src/components/Project/LiveUserProfile/index.js
rename to cocode/src/components/Live/LiveUsers/index.js
index d2691b77..108b8459 100644
--- a/cocode/src/components/Project/LiveUserProfile/index.js
+++ b/cocode/src/components/Live/LiveUsers/index.js
@@ -16,11 +16,13 @@ function LiveUserProfile({ username, avatar }) {
);
}
-function LiveUsers({ owner: { username, avatar }, participants = [] }) {
+function LiveUsers({ owner, participants }) {
+ if (!participants.length) participants = [];
+
return (
- <>
+
OWNERS
-
+
USERS
{participants.map(({ username, avatar }, index) => {
return (
@@ -31,7 +33,7 @@ function LiveUsers({ owner: { username, avatar }, participants = [] }) {
/>
);
})}
- >
+
);
}
diff --git a/cocode/src/components/Project/LiveUserProfile/style.js b/cocode/src/components/Live/LiveUsers/style.js
similarity index 78%
rename from cocode/src/components/Project/LiveUserProfile/style.js
rename to cocode/src/components/Live/LiveUsers/style.js
index 8f4a7679..5bad1c07 100644
--- a/cocode/src/components/Project/LiveUserProfile/style.js
+++ b/cocode/src/components/Live/LiveUsers/style.js
@@ -1,6 +1,12 @@
import styled from 'styled-components';
import { TAB_CONTAINER_THEME, LIVE_TAB_THEME } from 'constants/theme';
+const Container = styled.div`
+ & {
+ margin: 1rem 0;
+ }
+`;
+
const Title = styled.h1`
& {
color: ${TAB_CONTAINER_THEME.tabContainerTitleColor};
@@ -12,13 +18,14 @@ const Title = styled.h1`
const UserProfile = styled.div`
& {
display: flex;
+ margin: 0.7rem 0;
}
`;
const UserName = styled.div`
& {
align-self: center;
- margin-left: 0.4rem;
+ margin-left: 0.5rem;
font-weight: 100;
font-size: 1rem;
}
@@ -38,4 +45,11 @@ const SelfLabel = styled(UserName)`
}
`;
-export { Title, UserProfile, UserName, UserAvatar, SelfLabel };
+export {
+ Container,
+ Title,
+ UserProfile,
+ UserName,
+ UserAvatar,
+ SelfLabel
+};
diff --git a/cocode/src/components/Project/BrowserV1/index.js b/cocode/src/components/Project/BrowserV1/index.js
deleted file mode 100644
index 6555dcf3..00000000
--- a/cocode/src/components/Project/BrowserV1/index.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import React, { useState, useEffect, useContext } from 'react';
-import * as Styled from './style';
-
-import ProjectContext from 'contexts/ProjectContext';
-// import { updateCodeActionCreator } from 'actions/Project';
-// import * as bundler from 'bundler';
-
-// import * as babel from '@babel/core';
-// import reactPreset from '@babel/preset-react';
-
-function BrowserV1({ code, ...props }) {
- const { project, dispatchProject } = useContext(ProjectContext);
- const { files, entry, selectedFileId } = project;
- const [fileSystem, setFileSystem] = useState({});
- // const buildCode = () => {
- // try {
- // const parsedCode = babel.transform(code, {
- // presets: [reactPreset]
- // });
- // eval(parsedCode.code);
- // } catch (e) {
- // console.log(e);
- // }
- // };
-
- // useEffect(buildCode, [files]);
- // useEffect(() => {
- // const fileTemp = {};
- // Object.keys(bundler.exports).forEach(key => {
- // delete bundler.exports[key];
- // });
- // function fileParser(id, path = '') {
- // if (files[id].type !== 'directory') {
- // fileTemp[`${path}/${files[id].name}`] = {
- // contents: files[id].contents
- // };
- // bundler.exports[`${path}/${files[id].name}`] = {
- // contents: files[id].contents
- // };
- // } else {
- // files[id].child.forEach(file => {
- // fileParser(file, `${path}/${files[id].name}`);
- // });
- // }
- // }
- // if (project) fileParser(project.root);
-
- // setFileSystem(fileTemp);
- // }, [files]);
-
- // useEffect(() => {
- // console.log(fileSystem);
- // try {
- // bundler.init();
- // bundler.require('/root/src/index');
- // } catch (error) {
- // console.log(error);
- // }
- // }, [fileSystem]);
- return ;
-}
-
-export default BrowserV1;
diff --git a/cocode/src/components/Project/BrowserV1/style.js b/cocode/src/components/Project/BrowserV1/style.js
deleted file mode 100644
index 0fa19cf7..00000000
--- a/cocode/src/components/Project/BrowserV1/style.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import styled from 'styled-components';
-
-const BrowserV1 = styled.div`
- & {
- height: ${({ height }) => height};
-
- background-color: white;
- color: black;
- }
-`;
-
-export { BrowserV1 };
diff --git a/cocode/src/components/Project/BrowserV2/index.js b/cocode/src/components/Project/BrowserV2/index.js
index 702a5276..e7c8a6b7 100644
--- a/cocode/src/components/Project/BrowserV2/index.js
+++ b/cocode/src/components/Project/BrowserV2/index.js
@@ -8,10 +8,10 @@ import React, {
import { useParams } from 'react-router-dom';
import * as Styled from './style';
+import search from './search.svg';
import addToast from 'components/Common/Toast';
-import CoconutSpinner from 'components/Common/CoconutSpinner';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import { installDependencyActionCreator } from 'actions/Project';
@@ -24,38 +24,31 @@ import getUpdatedPackageJSON from 'pages/Project/getUpdatedPackageJSON';
import { COCONUT_SERVER } from 'config';
import * as NOTIFICATION from 'constants/notificationMessage';
+import { KEY_CODE_ENTER } from 'constants/keyCode';
// Constants
const MIN_WAIT_TIME = 1500;
const UPDATE_PROJECT = 'updateProject';
+const PROTOCOLS = ['http://', 'https://'];
function BrowserV2({ ...props }) {
const { projectId } = useParams();
+ const DEFAULT_URL = `${COCONUT_SERVER}/${projectId}`;
+
const { project, dispatchProject } = useContext(ProjectContext);
const [{ data, error }, setRequest] = useFetch({});
const [isReadyToReceiveMessage, setIsReadyToReceiveMessage] = useState(
false
);
const [dependency, setDependency] = useState(undefined);
- const [isBuildingCoconut, setIsBuildingCoconut] = useState(true);
+ const [addressInputURL, setAddressInput] = useState(DEFAULT_URL);
+
const iframeReference = useRef();
+ const addressReference = useRef();
const { files, root, dependencyInstalling } = project;
- const handleComponentDidMount = () => {
- window.addEventListener('message', receiveMsgFromChild);
- };
-
- const receiveMsgFromChild = e => {
- const { command, dependency } = e.data;
-
- const cocodeActions = { buildEnd };
- cocodeActions[command] && cocodeActions[command](dependency);
- };
-
- const buildEnd = () => setIsBuildingCoconut(false);
-
const endInstallDependency = useCallback(dependency => {
setTimeout(() => {
const installDependencyAction = installDependencyActionCreator({
@@ -66,6 +59,9 @@ function BrowserV2({ ...props }) {
}, MIN_WAIT_TIME);
});
+ const isHaveProtocol = (value) =>
+ PROTOCOLS.some(PROTOCOL => value.includes(PROTOCOL));
+
const handleUpdateDependency = () => {
if (!isReadyToReceiveMessage) return;
if (!dependencyInstalling) return;
@@ -115,6 +111,20 @@ function BrowserV2({ ...props }) {
addToast.error(NOTIFICATION.FAIL_INSTALL_DEPENDENCY);
};
+ const handleAddressInputKeyDown = ({ keyCode, target: { value } }) => {
+ if (keyCode === KEY_CODE_ENTER) {
+ const address = isHaveProtocol(value) ? value : `${PROTOCOLS[0]}${value}`;
+ setAddressInput(address);
+ addressReference.current.value = address;
+ }
+ };
+
+ const handleChangeCurrentURL = () => {
+ const address = `${COCONUT_SERVER}/${projectId}`;
+ setAddressInput(address);
+ addressReference.current.value = address;
+ };
+
const handleIframeOnLoad = useCallback(() => {
setIsReadyToReceiveMessage(true);
@@ -127,7 +137,7 @@ function BrowserV2({ ...props }) {
iframeReference.current.contentWindow.postMessage(data, '*');
}, [project]);
- useEffect(handleComponentDidMount, []);
+ useEffect(handleChangeCurrentURL, [projectId]);
useEffect(handleUpdateDependency, [dependencyInstalling]);
useEffect(handleUpdateFile, [files]);
@@ -136,15 +146,18 @@ function BrowserV2({ ...props }) {
return (
- {isBuildingCoconut && (
-
-
- Please wait to build complete...
-
- )}
+
+
+
+
diff --git a/cocode/src/components/Project/BrowserV2/search.svg b/cocode/src/components/Project/BrowserV2/search.svg
new file mode 100644
index 00000000..a1739b09
--- /dev/null
+++ b/cocode/src/components/Project/BrowserV2/search.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/components/Project/BrowserV2/style.js b/cocode/src/components/Project/BrowserV2/style.js
index 5c57a943..4b547453 100644
--- a/cocode/src/components/Project/BrowserV2/style.js
+++ b/cocode/src/components/Project/BrowserV2/style.js
@@ -1,37 +1,17 @@
import styled from 'styled-components';
+import { BROWSER_THEME } from 'constants/theme';
const Frame = styled.div`
& {
position: relative;
}
`;
-const ErrorDisplay = styled.div`
- & {
- position: absolute;
- z-index: ${({ errorDescription }) => (errorDescription ? 1 : -1)};
- overflow-x: scroll;
-
- padding: 1rem;
-
- height: 100%;
- width: 100%;
-
- background-color: ${({ errorDescription }) =>
- errorDescription ? 'rgba(0, 0, 0, 0.7)' : 'transparent'};
-
- font-size: 2rem;
- font-weight: lighter;
- }
-`;
const BrowserV2 = styled.iframe`
& {
- position: absolute;
-
- height: 100%;
+ height: calc(100% - 3.1rem);
width: 100%;
-
- background-color: white;
+ background-color: ${BROWSER_THEME.iframeBGColor};
}
`;
@@ -50,15 +30,51 @@ const LoadingOverlay = styled.section`
justify-content: center;
align-items: center;
- background-color: black;
+ background-color: ${BROWSER_THEME.loadingOverlayBGColor};
p {
margin-top: 2rem;
-
font-size: 3rem;
font-weight: lighter;
}
}
`;
-export { Frame, ErrorDisplay, BrowserV2, LoadingOverlay };
+const AddressContainer = styled.div`
+ & {
+ display: flex;
+ align-items: center;
+ height: 3.1rem;
+ width: 100%;
+ padding: 0.8rem;
+ background: ${BROWSER_THEME.browserHeaderBGColor};
+ font-size: 1rem;
+ }
+`;
+
+const AddressInput = styled.input`
+ & {
+ width: 100%;
+ height: 100%;
+ padding: 0.3rem;
+ background: ${BROWSER_THEME.addressInputBGColor};
+ color: ${BROWSER_THEME.addressInputTextColor};
+ }
+`;
+
+const SearchIcon = styled.img`
+ & {
+ height: 100%;
+ padding: 0.4rem 0;
+ background: ${BROWSER_THEME.addressInputBGColor};
+ }
+`;
+
+export {
+ Frame,
+ BrowserV2,
+ LoadingOverlay,
+ AddressContainer,
+ AddressInput,
+ SearchIcon
+};
diff --git a/cocode/src/components/Project/Directory/index.js b/cocode/src/components/Project/Directory/index.js
index 2a9d2813..de1f9c2c 100644
--- a/cocode/src/components/Project/Directory/index.js
+++ b/cocode/src/components/Project/Directory/index.js
@@ -7,7 +7,7 @@ import DropZone from 'components/Common/DropZone';
import File from 'components/Project/File';
import NewFile from 'components/Project/NewFile';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import { EXPLORER_TAB_CONTAINER_THEME } from 'constants/theme';
import * as NOTIFICATION from 'constants/notificationMessage';
@@ -37,11 +37,11 @@ function isProtectedFile({ files, root, entry, fileId }) {
return false;
}
-function isFileNotMoveable({ files, fileId, newParentId }) {
+function isFileNotMovable({ files, fileId, newParentId }) {
const fileName = files[fileId].name;
- const childsOfNewParent = files[newParentId].child;
+ const childrenOfNewParent = files[newParentId].child;
- return childsOfNewParent
+ return childrenOfNewParent
.map(id => files[id].name)
.some(name => name === fileName);
}
@@ -93,7 +93,7 @@ function Directory({
.filter(isNotPackageJSON)
: [];
- // Evnet handler
+ // Event handler
const handleToggleDirectory = () =>
setToggleDirectoryOpen(!toggleDirectoryOpen);
const handleEditFileName = changedName => {
@@ -114,7 +114,7 @@ function Directory({
if (
fileId === id ||
isProtectedFile({ files, root, entry, fileId }) ||
- isFileNotMoveable({ files, fileId, newParentId: id })
+ isFileNotMovable({ files, fileId, newParentId: id })
)
return addToast.error(NOTIFICATION.FILE_IS_NOT_MOVABLE);
diff --git a/cocode/src/components/Project/File/index.js b/cocode/src/components/Project/File/index.js
index b51b6177..fa2f5f47 100644
--- a/cocode/src/components/Project/File/index.js
+++ b/cocode/src/components/Project/File/index.js
@@ -25,7 +25,7 @@ import { deleteFileAPICreator, updateFileAPICreator } from 'apis/File';
import { DELETE_FILE, UPDATE_FILE_NAME } from 'actions/types';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
// Constants
const API_NOTIFICATION = {
@@ -34,9 +34,11 @@ const API_NOTIFICATION = {
};
function isNotChangeableFileName({ files, changedName, parentId }) {
- const childsOfParent = files[parentId].child;
+ if (!changedName.trim().length) return true;
- return childsOfParent
+ const childrenOfParent = files[parentId].child;
+
+ return childrenOfParent
.map(id => files[id].name)
.some(name => name === changedName);
}
@@ -60,7 +62,7 @@ function File({
const [toggleEdit, setToggleEdit] = useState(false);
const [requestedAPI, setRequestedAPI] = useState(null);
- const nameEditReferenece = useRef(null);
+ const nameEditReference = useRef(null);
const [{ data, error }, setRequest] = useFetch({});
const {
@@ -71,7 +73,7 @@ function File({
const successHandler = {
[DELETE_FILE]: handleDeleteFile,
[UPDATE_FILE_NAME]: () => {
- const changedName = nameEditReferenece.current.textContent;
+ const changedName = nameEditReference.current.textContent;
setFileName(changedName);
handleEditFileName(changedName);
}
@@ -87,7 +89,7 @@ function File({
return;
}
- changeDivEditable(nameEditReferenece.current, true);
+ changeDivEditable(nameEditReference.current, true);
setToggleEdit(true);
};
@@ -138,7 +140,7 @@ function File({
const handleKeyDown = e => {
if (e.keyCode === KEY_CODE_ENTER) {
setToggleEdit(false);
- nameEditReferenece.current.contentEditable = false;
+ nameEditReference.current.contentEditable = false;
}
};
@@ -177,7 +179,7 @@ function File({
>
{
@@ -76,17 +76,17 @@ function NewFile({ depth, type, parentId, handleEndCreateFile }) {
type
});
dispatchProject(createFileAction);
- changeDivEditable(fileNameInputReferenece.current, false);
+ changeDivEditable(fileNameInputReference.current, false);
};
const handleErrorResponse = () => {
if (!error) return;
addToast.error(NOTIFICATION.FAIL_TO_CREATE_FILE);
- changeDivEditable(fileNameInputReferenece.current, false);
+ changeDivEditable(fileNameInputReference.current, false);
};
useEffect(() => {
- fileNameInputReferenece.current.focus();
+ fileNameInputReference.current.focus();
}, []);
useEffect(handleSetNewFileState, [data]);
useEffect(handleErrorResponse, [error]);
@@ -95,7 +95,7 @@ function NewFile({ depth, type, parentId, handleEndCreateFile }) {
`${API_SERVER}/dependency/search?name=${name}`
};
-const DEPENDENCY = {
- modules: `${DEPENDENCY_SERVER}/modules`
+export {
+ DEFAULT_REQUEST_OPTION,
+ API,
+ COCONUT_SERVER,
+ LIVE_SERVER,
+ COCODE_SERVER
};
-
-export { DEFAULT_REQUEST_OPTION, API, DEPENDENCY, COCONUT_SERVER };
diff --git a/cocode/src/constants/fileImagesSrc.js b/cocode/src/constants/fileImagesSrc.js
index 8f0fbeef..4361fdd5 100644
--- a/cocode/src/constants/fileImagesSrc.js
+++ b/cocode/src/constants/fileImagesSrc.js
@@ -1,16 +1,11 @@
const FILE_IMAGES_SRC = {
file: 'https://codesandbox.io/static/media/file.6cbc0ce8.svg',
directory: 'https://codesandbox.io/static/media/folder.30a30d83.svg',
- directoryOpen:
- 'https://codesandbox.io/static/media/folder-open.df474ba4.svg',
- js:
- 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/javascript.svg',
- css:
- 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/css.svg',
- html:
- 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/html.svg',
- npm:
- 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/npm.svg'
+ directoryOpen: 'https://codesandbox.io/static/media/folder-open.df474ba4.svg',
+ js: 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/javascript.svg',
+ css: 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/css.svg',
+ html: 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/html.svg',
+ npm: 'https://cdn.jsdelivr.net/gh/PKief/vscode-material-icon-theme@master/icons/npm.svg'
};
export default FILE_IMAGES_SRC;
diff --git a/cocode/src/constants/notificationMessage.js b/cocode/src/constants/notificationMessage.js
index e4b69809..f312f88c 100644
--- a/cocode/src/constants/notificationMessage.js
+++ b/cocode/src/constants/notificationMessage.js
@@ -14,6 +14,13 @@ const CONFIRM_DELETE_FILE = 'Are you delete this file?';
const CONFIRM_DELETE_COCONUT = 'Are you delete this coconut?';
const LOADING_DASHBOARD = 'Please wait to fetch coconuts';
+const LOADING_PROJECT = 'Please wait to fetch coconut';
+const LOADING_LIVE = 'Please wait to connect live share';
+
+const SUCCESS_FORK = 'Forked Coconut, Success!';
+const CONFLICT_FORK = 'Already forked! Enjoy Coconut!';
+
+const SHUT_DOWN_LIVE_SHARE = 'The live share has been shut down';
export {
FAIL_INSTALL_DEPENDENCY,
@@ -29,4 +36,9 @@ export {
CONFIRM_DELETE_FILE,
CONFIRM_DELETE_COCONUT,
LOADING_DASHBOARD,
+ LOADING_PROJECT,
+ LOADING_LIVE,
+ SUCCESS_FORK,
+ CONFLICT_FORK,
+ SHUT_DOWN_LIVE_SHARE,
};
diff --git a/cocode/src/constants/theme.js b/cocode/src/constants/theme.js
index dba7d1b5..04a17bec 100644
--- a/cocode/src/constants/theme.js
+++ b/cocode/src/constants/theme.js
@@ -6,6 +6,7 @@ const DEFAULT_THEME = {
textColor: '#ffffff',
exceptHeaderHeight: '88vh',
+ headerMinHeight: '9vh',
headerHeight: '12vh'
};
@@ -16,11 +17,11 @@ const DROPDOWN_THEME = {
};
const BROWSER_THEME = {
+ iframeBGColor: '#ffffff',
+ loadingOverlayBGColor: '#000000',
browserHeaderBGColor: '#1d2022',
- addressInputBGColor: '#000',
- addressInputTextColor: '#fff',
-
- browserHeight: '88vh'
+ addressInputBGColor: '#000000',
+ addressInputTextColor: '#ffffff',
};
const TAB_CONTAINER_THEME = {
@@ -100,12 +101,13 @@ const INFO_TAB_THEME = {
};
const LIVE_TAB_THEME = {
- liveButtonBGColorHover: '#C74040B2',
- liveButtonBGColor: '#880000',
+ liveButtonBGColorHover: '#880000',
+ liveButtonBGColor: '#880000ba',
liveFontColor: '#bcbcbc',
liveStatusLabelColor: '#c74040b3',
liveLinkBGColor: '#000000',
- liveSelfLabelColor: '#333333'
+ liveSelfLabelColor: '#676767',
+ liveCircleBGColor: '#ffffff'
};
const FILE_TAB_THEME = {
@@ -125,6 +127,12 @@ const TOAST_THEME = {
toastErrorDeco: 'rgba(233,42,61,0.98)'
};
+const SIGN_IN_THEME = {
+ signInButtonBGColor: '#e7e7e7',
+ signInButtonBGHoverColor: '#ffffff',
+ signInButtonTextColor: '#000000',
+};
+
export {
DEFAULT_THEME,
DROPDOWN_THEME,
@@ -138,5 +146,6 @@ export {
FILE_TAB_THEME,
MONACO_THEME,
LIVE_TAB_THEME,
- TOAST_THEME
+ TOAST_THEME,
+ SIGN_IN_THEME
};
diff --git a/cocode/src/containers/Common/Header/index.js b/cocode/src/containers/Common/Header/index.js
index fe8cf8dd..2190d833 100644
--- a/cocode/src/containers/Common/Header/index.js
+++ b/cocode/src/containers/Common/Header/index.js
@@ -2,7 +2,7 @@ import React, { useState, useContext } from 'react';
import * as Styled from './style';
import { Link, useHistory } from 'react-router-dom';
-import deleteCookie from 'utils/deleteCookie';
+import { deleteCookie } from 'utils/controlCookie';
import Logo from 'components/Common/Logo';
import Modal from 'components/Common/Modal';
@@ -10,20 +10,24 @@ import UserProfile from 'components/Common/UserProfile';
import ModalPortal from 'components/Common/ModalPortal';
import LoginModalBody from 'components/Common/LoginModalBody';
-import UserContext from 'contexts/UserContext';
+import { UserContext } from 'contexts';
-function Header() {
+const CONFIRM_LOGOUT = '로그아웃 하시겠습니까?';
+
+function Header({ name }) {
const history = useHistory();
- const { user } = useContext(UserContext);
+ const { user, setUser } = useContext(UserContext);
const [isSignInModalOpen, setIsSignInModalOpen] = useState(false);
const handleOpenSignInModal = () => setIsSignInModalOpen(true);
const handleCloseSignInModal = () => setIsSignInModalOpen(false);
const handleClickDashBoard = () => history.push('/dashboard');
const handleSignOut = () => {
- const confirm = window.confirm('로그아웃 하시겠습니까?');
+ const confirm = window.confirm(CONFIRM_LOGOUT);
if (!confirm) return;
deleteCookie('jwt');
+ setUser(null);
+ history.replace('../');
};
const profileDropDownMenuItems = [
@@ -38,14 +42,12 @@ function Header() {
];
return (
-
+
- {/*
- History
- */}
-
+ {name || ''}
+
{user ? (
)}
-
+
);
}
diff --git a/cocode/src/containers/Common/Header/style.js b/cocode/src/containers/Common/Header/style.js
index 61ab4b5c..d0c5f304 100644
--- a/cocode/src/containers/Common/Header/style.js
+++ b/cocode/src/containers/Common/Header/style.js
@@ -4,32 +4,20 @@ const Header = styled.header`
& {
display: flex;
flex-direction: row;
- justify-content: flex-start;
+ justify-content: space-between;
align-items: center;
- height: ${({ theme }) => theme.headerHeight};
+ height: ${({ theme, isMinHeight }) =>
+ isMinHeight ? theme.headerMinHeight : theme.headerHeight};
background-color: ${({ theme }) => theme.backgroundColor};
padding: 2rem 2.3rem;
}
`;
-const HeaderCategory = styled.button`
+const ProjectName = styled.div`
& {
- margin-left: 1.5rem;
-
- font-size: 1.4rem;
- font-weight: 100;
- }
-
- &:hover {
- color: ${({ theme }) => theme.mainColor};
- }
-`;
-
-const HeaderRightSideArea = styled.div`
- & {
- margin-left: auto;
+ font-size: 1.3rem;
}
`;
@@ -44,4 +32,4 @@ const SignInButton = styled.button`
}
`;
-export { Header, SignInButton, HeaderCategory, HeaderRightSideArea };
+export { Header, SignInButton, ProjectName };
diff --git a/cocode/src/containers/Common/LoadingSpinner/index.js b/cocode/src/containers/Common/LoadingSpinner/index.js
new file mode 100644
index 00000000..f6dfb12d
--- /dev/null
+++ b/cocode/src/containers/Common/LoadingSpinner/index.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import * as Styled from './style';
+import CoconutSpinner from 'components/Common/CoconutSpinner';
+
+function LoadingSpinner({ message }) {
+ return (
+
+
+ {message}
+
+ );
+}
+
+export default LoadingSpinner;
diff --git a/cocode/src/pages/DashBoard/style.js b/cocode/src/containers/Common/LoadingSpinner/style.js
similarity index 100%
rename from cocode/src/pages/DashBoard/style.js
rename to cocode/src/containers/Common/LoadingSpinner/style.js
diff --git a/cocode/src/containers/DashBoard/ProjectCardList/style.js b/cocode/src/containers/DashBoard/ProjectCardList/style.js
index 72c9ab4c..bd6b1488 100644
--- a/cocode/src/containers/DashBoard/ProjectCardList/style.js
+++ b/cocode/src/containers/DashBoard/ProjectCardList/style.js
@@ -1,7 +1,7 @@
import styled from 'styled-components';
const Main = styled.main`
- height: 100%;
+ height: 88vh;
padding: 3rem;
`;
diff --git a/cocode/src/containers/Live/DependencyTab/index.js b/cocode/src/containers/Live/DependencyTab/index.js
new file mode 100644
index 00000000..ec529a8d
--- /dev/null
+++ b/cocode/src/containers/Live/DependencyTab/index.js
@@ -0,0 +1,44 @@
+import React, { useContext } from 'react';
+import * as Styled from './style';
+
+import CoconutSpinner from 'components/Common/CoconutSpinner';
+import Dependency from 'components/Project/Dependency';
+import DependencyNow from 'components/Project/DependencyNow';
+import DependencySearch from 'components/Project/DependencySearch';
+
+import { ProjectContext } from 'contexts';
+
+const TabTitleFirst = 'DEPENDENCIES';
+const TabTitleSecond = 'SEARCH DEPENDENCY';
+
+function InstallingDisplay() {
+ return (
+
+
+
+ Please wait to install module...
+
+
+ );
+}
+
+function DependencyTab() {
+ const { project } = useContext(ProjectContext);
+ const { dependencyInstalling } = project;
+
+ return (
+
+ {dependencyInstalling && }
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default DependencyTab;
diff --git a/cocode/src/containers/Live/DependencyTab/style.js b/cocode/src/containers/Live/DependencyTab/style.js
new file mode 100644
index 00000000..f036d895
--- /dev/null
+++ b/cocode/src/containers/Live/DependencyTab/style.js
@@ -0,0 +1,47 @@
+import styled from 'styled-components';
+
+const Frame = styled.div`
+ & {
+ position: relative;
+
+ height: 100%;
+ }
+
+ & > * {
+ width: 100%;
+ }
+`;
+
+const InstallingDisplay = styled.div`
+ & {
+ position: absolute;
+ z-index: 1;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ height: 100%;
+ width: 100%;
+
+ background-color: rgba(0, 0, 0, 0.7);
+ }
+`;
+
+const InstallPhrase = styled.p`
+ & {
+ margin-top: 2rem;
+
+ font-size: 1rem;
+ font-weight: lighter;
+ }
+`;
+
+const DependencyArea = styled.div`
+ & {
+ position: absolute;
+ }
+`;
+
+export { Frame, InstallingDisplay, InstallPhrase, DependencyArea };
diff --git a/cocode/src/containers/Live/Editor/index.js b/cocode/src/containers/Live/Editor/index.js
new file mode 100644
index 00000000..c75558fc
--- /dev/null
+++ b/cocode/src/containers/Live/Editor/index.js
@@ -0,0 +1,221 @@
+import React, { useState, useContext, useEffect, useRef } from 'react';
+import { useParams } from 'react-router-dom';
+import * as Styled from './style';
+
+import FileTabBar from 'components/Project/FileTabBar';
+import MonacoEditor from 'components/Project/MonacoEditor';
+
+import { LiveContext, UserContext, ProjectContext } from 'contexts';
+import {
+ updateCodeActionCreator,
+ updateCodeFromFileIdActionCreator
+} from 'actions/Project';
+
+import useFetch from 'hooks/useFetch';
+
+import { CursorWidget } from 'utils/monacoWidget';
+
+let timer;
+const DEBOUNCING_TIME = 1000;
+const EVENT_DELAY = 10;
+
+const MAX_RANGE = {
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 9999,
+ endColumn: 9999
+};
+
+const userCursor = {};
+
+function Editor({ handleForkCoconut }) {
+ const { user } = useContext(UserContext);
+ const { projectId } = useParams();
+ const { project, dispatchProject } = useContext(ProjectContext);
+ const { socket } = useContext(LiveContext);
+ const [code, setCode] = useState(project.editingCode);
+ const [isEditorMounted, setIsEditorMounted] = useState(false);
+ const [_, setRequest] = useFetch({});
+
+ const [fileSelectFlag, setFileSelectFlag] = useState(undefined);
+ const { selectedFileId, files } = project;
+
+ const editorRef = useRef();
+ const isBusy = useRef(true);
+ const pendingEvent = useRef(false);
+ const selectedRef = useRef();
+ const filesRef = useRef();
+
+ const handleOnChangeCodeInMonaco = (_, changedCode) => {
+ if (timer) clearTimeout(timer);
+
+ timer = setTimeout(() => {
+ setCode(changedCode);
+ }, DEBOUNCING_TIME);
+ };
+
+ const handleChnageSelectedFileMonaco = (
+ source,
+ text,
+ range = MAX_RANGE
+ ) => {
+ isBusy.current = true;
+ if (editorRef.current) {
+ editorRef.current.executeEdits(source, [
+ {
+ range,
+ text,
+ forceMoveMarkers: true
+ }
+ ]);
+ }
+ setTimeout(() => {
+ isBusy.current = false;
+ }, 0);
+ };
+
+ const handleChangedSelectedFile = () => {
+ if (!project) return;
+ if (!filesRef.current) return;
+ selectedRef.current = selectedFileId;
+ setCode(project.editingCode);
+
+ handleChnageSelectedFileMonaco(
+ 'changeFile',
+ filesRef.current[selectedFileId].contents
+ );
+ };
+
+ const handleUpdateCode = () => {
+ if (fileSelectFlag !== selectedFileId) {
+ setFileSelectFlag(selectedFileId);
+ return;
+ }
+ const updateCodeAction = updateCodeActionCreator({
+ changedCode: code
+ });
+ dispatchProject(updateCodeAction);
+ };
+
+ const handleEmit = (e, timeStamp) => {
+ if (isBusy.current) return;
+ if (!timeStamp) timeStamp = Date();
+ // if (pendingEvent.current) handleEmit(e, timeStamp);
+ const change = e.changes[0];
+ const operation = {
+ rangeLength: change.rangeLength,
+ rangeOffset: change.rangeOffset,
+ text: change.text.replace(/\r\n/g, '\n'),
+ timeStamp: timeStamp
+ };
+ if (!socket) return;
+ // pendingEvent.current = true;
+ socket.emit('change', selectedRef.current, operation);
+ };
+
+ const handleCursor = e => {
+ if (!socket) return;
+ socket.emit('moveCursor', selectedRef.current, e.position);
+ };
+
+ const handleEditorDidMount = (_, editor) => {
+ editorRef.current = editor;
+ editor.onDidChangeModelContent(handleEmit);
+ editor.onDidChangeCursorPosition(handleCursor);
+ setIsEditorMounted(true);
+ };
+
+ useEffect(handleUpdateCode, [code]);
+ useEffect(handleChangedSelectedFile, [project.selectedFileId]);
+
+ useEffect(() => {
+ if (!isEditorMounted) return;
+ selectedRef.current = selectedFileId;
+ isBusy.current = true;
+ handleChnageSelectedFileMonaco('initial', project.editingCode);
+ }, [isEditorMounted]);
+
+ useEffect(() => {
+ //initialize
+ if (!socket) return;
+ if (!isEditorMounted) return;
+ isBusy.current = false;
+ filesRef.current = JSON.parse(JSON.stringify(files));
+ socket.on('change', handleOnChangeCode);
+ socket.on('moveCursor', handleMoveCursor);
+ }, [socket, isEditorMounted]);
+
+ const handleOnChangeCode = (socketId, fileId, op) => {
+ if (socket.id === socketId) {
+ setTimeout(() => {
+ pendingEvent.current = false;
+ }, EVENT_DELAY);
+ return;
+ }
+
+ if (selectedRef.current !== fileId) {
+ const originCode = filesRef.current[fileId].contents;
+
+ const str1 = originCode.slice(0, op.rangeOffset);
+ const str2 = originCode.slice(op.rangeOffset + op.rangeLength);
+ const changedCode = `${str1}${op.text}${str2}`;
+ filesRef.current[fileId].contents = changedCode;
+ const updateCodeFromFileIdAction = updateCodeFromFileIdActionCreator(
+ {
+ fileId,
+ changedCode
+ }
+ );
+ dispatchProject(updateCodeFromFileIdAction);
+ return;
+ }
+
+ const rangeOffset = op.rangeOffset;
+ const rangeLength = op.rangeLength;
+ const text = op.text;
+
+ const startPosition = editorRef.current
+ .getModel()
+ .getPositionAt(rangeOffset);
+ const endPosition = editorRef.current
+ .getModel()
+ .getPositionAt(rangeOffset + rangeLength);
+
+ handleChnageSelectedFileMonaco(socketId, text, {
+ startLineNumber: startPosition.lineNumber,
+ startColumn: startPosition.column,
+ endLineNumber: endPosition.lineNumber,
+ endColumn: endPosition.column
+ });
+ };
+
+ const handleMoveCursor = (username, fileId, position) => {
+ if (!userCursor[username]) {
+ const widget = new CursorWidget(
+ editorRef.current,
+ username,
+ position
+ );
+ userCursor[username] = widget;
+ editorRef.current.addContentWidget(widget);
+ }
+ if (selectedRef.current === fileId)
+ userCursor[username].showCursor(position);
+ else userCursor[username].hiddenCursor();
+ };
+
+ return (
+
+
+
+
+ );
+}
+
+export default Editor;
diff --git a/cocode/src/containers/Live/Editor/style.js b/cocode/src/containers/Live/Editor/style.js
new file mode 100644
index 00000000..24eb50c5
--- /dev/null
+++ b/cocode/src/containers/Live/Editor/style.js
@@ -0,0 +1,14 @@
+import styled from 'styled-components';
+
+const Editor = styled.section`
+ & {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .Stretch-width {
+ flex-grow: 2;
+ }
+`;
+
+export { Editor };
diff --git a/cocode/src/containers/Live/ExplorerTab/index.js b/cocode/src/containers/Live/ExplorerTab/index.js
new file mode 100644
index 00000000..a3afa388
--- /dev/null
+++ b/cocode/src/containers/Live/ExplorerTab/index.js
@@ -0,0 +1,104 @@
+import React, { useState, useContext } from 'react';
+import * as Styled from './style';
+
+import {
+ NewFolderIcon,
+ NewFileIcon
+} from 'components/Project/ExplorerTabIcons';
+import Directory from 'components/Project/Directory';
+import NewFile from 'components/Project/NewFile';
+
+import { ProjectContext } from 'contexts';
+import {
+ selectFileActionCreator,
+ updateFileNameActionCreator,
+ deleteFileActionCreator,
+ moveFileActionCreator
+} from 'actions/Project';
+
+const TAB_TITLE = 'EXPLORER';
+
+function TabHeader({ handleCreateFile }) {
+ return (
+
+ {TAB_TITLE}
+
+
+
+
+
+ );
+}
+
+function ExplorerTab() {
+ const [isNewFileCreating, setIsNewFileCreating] = useState(false);
+ const [createFileType, setCreateFileType] = useState(null);
+
+ const { project, dispatchProject } = useContext(ProjectContext);
+ const { files, root } = project;
+ const childIdsInRoot = files[root].child;
+
+ const handleCreateFile = type => {
+ setCreateFileType(type);
+ setIsNewFileCreating(true);
+ };
+
+ const handleEndCreateFile = () => setIsNewFileCreating(false);
+
+ const handleSelectFile = selectedFileId => {
+ const selectFileAction = selectFileActionCreator({ selectedFileId });
+ dispatchProject(selectFileAction);
+ };
+
+ const handleEditFileName = (selectedFileId, changedName) => {
+ const updateFileNameAction = updateFileNameActionCreator({
+ selectedFileId,
+ changedName
+ });
+ dispatchProject(updateFileNameAction);
+ };
+
+ const handleDeleteFile = deleteFileId => {
+ const deleteFileAction = deleteFileActionCreator({ deleteFileId });
+ dispatchProject(deleteFileAction);
+ };
+
+ const handleMoveFile = (directoryId, fileId) => {
+ const moveFileAction = moveFileActionCreator({
+ directoryId,
+ fileId
+ });
+ dispatchProject(moveFileAction);
+ };
+
+ return (
+ <>
+
+ {isNewFileCreating && (
+
+ )}
+
+
+
+ >
+ );
+}
+
+export default ExplorerTab;
diff --git a/cocode/src/containers/Live/ExplorerTab/style.js b/cocode/src/containers/Live/ExplorerTab/style.js
new file mode 100644
index 00000000..4c6f6aa1
--- /dev/null
+++ b/cocode/src/containers/Live/ExplorerTab/style.js
@@ -0,0 +1,67 @@
+import styled from 'styled-components';
+import {
+ TAB_CONTAINER_THEME,
+ EXPLORER_TAB_CONTAINER_THEME
+} from 'constants/theme';
+
+const {
+ explorerTabContainerSelectedFileBGColor,
+} = EXPLORER_TAB_CONTAINER_THEME;
+
+const {
+ tabContainerHeaderBGColor,
+ tabContainerTitleColor,
+ tabContainerTitleSize,
+ tabContainerTitleWeight
+} = TAB_CONTAINER_THEME;
+
+const TabBody = styled.div`
+ & {
+ height: 100%;
+ }
+
+ .Is-selected-file {
+ background-color: ${explorerTabContainerSelectedFileBGColor};
+ }
+`;
+
+const TabHeader = styled.header`
+ & {
+ display: flex;
+ flex-direction: row;
+ background-color: ${tabContainerHeaderBGColor};
+ }
+
+ .Tab-header-Side-icons {
+ margin: auto 0;
+ margin-left: auto;
+ margin-right: 1rem;
+ }
+`;
+
+const Title = styled.h1`
+ & {
+ padding: 0.7rem 1rem;
+
+ color: ${tabContainerTitleColor};
+ font-size: ${tabContainerTitleSize};
+ font-weight: ${tabContainerTitleWeight};
+ }
+`;
+
+const SideIcons = styled.span`
+ & {
+ display: flex;
+ flex-direction: row;
+
+ margin-left: auto;
+ }
+
+ & > svg {
+ margin: 0 0.2rem;
+
+ cursor: pointer;
+ }
+`;
+
+export { TabHeader, TabBody, Title, SideIcons };
diff --git a/cocode/src/containers/Live/LiveOnTab/close.svg b/cocode/src/containers/Live/LiveOnTab/close.svg
new file mode 100644
index 00000000..6adde6f1
--- /dev/null
+++ b/cocode/src/containers/Live/LiveOnTab/close.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/containers/Live/LiveOnTab/copy.svg b/cocode/src/containers/Live/LiveOnTab/copy.svg
new file mode 100644
index 00000000..912733d5
--- /dev/null
+++ b/cocode/src/containers/Live/LiveOnTab/copy.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/containers/Live/LiveOnTab/index.js b/cocode/src/containers/Live/LiveOnTab/index.js
new file mode 100644
index 00000000..61529764
--- /dev/null
+++ b/cocode/src/containers/Live/LiveOnTab/index.js
@@ -0,0 +1,59 @@
+import React, { useContext, useRef } from 'react';
+import { useParams, useHistory } from 'react-router-dom';
+import * as Styled from './style';
+import { LiveContext, UserContext } from 'contexts';
+
+import copy from './copy.svg';
+import close from './close.svg';
+import LiveUsers from 'components/Live/LiveUsers';
+
+import { liveOffActionCreator } from 'actions/Live';
+import { copyToClipboard } from 'utils/domControl';
+
+const LIVE_STATUS_LABEL = 'You’ve gone live!';
+const ON_BUTTON_LABEL = 'Stop Live';
+const ON_DESCRIPTION =
+ 'Share this link with others to invite them to the live.';
+
+function LiveOnTab() {
+ const link = useRef();
+ const history = useHistory();
+ const { projectId } = useParams();
+ const { user } = useContext(UserContext);
+ const { url, socket, participants, owner, dispatchLive } = useContext(LiveContext);
+
+ const handleCloseSocket = () => dispatchLive(liveOffActionCreator());
+
+ const handleDisconnectSocket = () => {
+ socket.emit('close');
+ socket.on('close', handleCloseSocket);
+ history.replace(`../project/${projectId}`);
+ };
+
+ const handleCopyLink = () => copyToClipboard(link.current);
+
+ return (
+
+
+
+ {LIVE_STATUS_LABEL}
+
+ {ON_DESCRIPTION}
+
+
+ {url}
+
+ {user === owner ? (
+
+
+ {ON_BUTTON_LABEL}
+
+ ) : ''}
+ {owner ? (
+
+ ) : ''}
+
+ );
+}
+
+export default LiveOnTab;
\ No newline at end of file
diff --git a/cocode/src/containers/Live/LiveOnTab/style.js b/cocode/src/containers/Live/LiveOnTab/style.js
new file mode 100644
index 00000000..29a21573
--- /dev/null
+++ b/cocode/src/containers/Live/LiveOnTab/style.js
@@ -0,0 +1,99 @@
+import styled from 'styled-components';
+import { LIVE_TAB_THEME } from 'constants/theme';
+
+const Container = styled.div`
+ & {
+ margin: 0.7rem 1rem;
+ }
+`;
+
+const Description = styled.div`
+ & {
+ margin-bottom: 1rem;
+ color: ${LIVE_TAB_THEME.liveFontColor};
+ font-size: 1rem;
+ font-weight: 100;
+ }
+`;
+
+const Button = styled.button`
+ & {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: -webkit-fill-available;
+ padding: 0.7rem 2.2rem;
+ border-radius: 0.7rem;
+ background-color: ${LIVE_TAB_THEME.liveButtonBGColor};
+ font-size: 1rem;
+ font-weight: 400;
+ }
+
+ &:hover {
+ background-color: ${LIVE_TAB_THEME.liveButtonBGColorHover};
+ }
+`;
+
+const Close = styled.img`
+ & {
+ width: 0.8rem;
+ height: 0.8rem;
+ margin-right: 0.5rem;
+ }
+`;
+
+const LiveStatusLabel = styled.div`
+ & {
+ display: flex;
+ margin-bottom: 1rem;
+ color: ${LIVE_TAB_THEME.liveStatusLabelColor};
+ font-size: 1.1rem;
+ font-weight: lighter;
+ }
+`;
+
+const LiveStatusSpan = styled.span`
+ & {
+ background-color: ${LIVE_TAB_THEME.liveStatusLabelColor};
+ margin: auto 1rem auto 0.3rem;
+ width: 0.6rem;
+ height: 0.6rem;
+ border-radius: 50%;
+ }
+`;
+
+const LinkURL = styled.div`
+ & {
+ display: flex;
+ width: 100%;
+ overflow: auto;
+ padding: 0.5rem 1rem;
+ margin-bottom: 0.5rem;
+ align-items: center;
+ background-color: ${LIVE_TAB_THEME.liveLinkBGColor};
+ color: ${LIVE_TAB_THEME.liveFontColor};
+ font-size: 0.9rem;
+ user-select: text;
+ }
+`;
+
+const Copy = styled.img`
+ & {
+ width: 0.8rem;
+ height: 0.8rem;
+ cursor: pointer;
+ margin-right: 0.5rem;
+ user-select: none;
+ }
+`;
+
+export {
+ Container,
+ Description,
+ Button,
+ Close,
+ LiveStatusLabel,
+ LiveStatusSpan,
+ LinkURL,
+ Copy
+};
diff --git a/cocode/src/containers/Live/LiveTab/index.js b/cocode/src/containers/Live/LiveTab/index.js
new file mode 100644
index 00000000..b2f78b93
--- /dev/null
+++ b/cocode/src/containers/Live/LiveTab/index.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import * as Styled from './style';
+
+import LiveOnTab from 'containers/Live/LiveOnTab';
+
+const TAB_TITLE = 'LIVE';
+
+function LiveTab() {
+ return (
+ <>
+ {TAB_TITLE}
+
+
+
+ >
+ );
+}
+
+export default LiveTab;
diff --git a/cocode/src/containers/Live/LiveTab/style.js b/cocode/src/containers/Live/LiveTab/style.js
new file mode 100644
index 00000000..d2378806
--- /dev/null
+++ b/cocode/src/containers/Live/LiveTab/style.js
@@ -0,0 +1,16 @@
+import styled from 'styled-components';
+import { TAB_CONTAINER_THEME } from 'constants/theme';
+
+const Title = styled.h1`
+ & {
+ padding: 0.7rem 1rem;
+ color: ${TAB_CONTAINER_THEME.tabContainerTitleColor};
+ font-size: ${TAB_CONTAINER_THEME.tabContainerTitleSize};
+ font-weight: ${TAB_CONTAINER_THEME.tabContainerTitleWeight};
+ background-color: ${TAB_CONTAINER_THEME.tabContainerHeaderBGColor};
+ }
+`;
+
+export {
+ Title
+};
diff --git a/cocode/src/containers/Live/TabBar/dependency.svg b/cocode/src/containers/Live/TabBar/dependency.svg
new file mode 100644
index 00000000..285034f5
--- /dev/null
+++ b/cocode/src/containers/Live/TabBar/dependency.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/containers/Live/TabBar/explorer.svg b/cocode/src/containers/Live/TabBar/explorer.svg
new file mode 100644
index 00000000..5f7c2792
--- /dev/null
+++ b/cocode/src/containers/Live/TabBar/explorer.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/containers/Live/TabBar/index.js b/cocode/src/containers/Live/TabBar/index.js
new file mode 100644
index 00000000..e8caca58
--- /dev/null
+++ b/cocode/src/containers/Live/TabBar/index.js
@@ -0,0 +1,44 @@
+import React, { useContext } from 'react';
+import * as Styled from './style';
+
+import { ProjectContext } from 'contexts';
+import TabIcon from 'components/Project/TabIcon';
+import Explorer from './explorer.svg';
+import Live from './live.svg';
+
+function TabBar({ theme }) {
+ const { clickedTabIndex, setClickedTabIndex } = useContext(ProjectContext);
+
+ const handleSetClickedIndex = index => setClickedTabIndex(index);
+
+ const tabIcons = [
+ {
+ name: 'explorer',
+ icon: Explorer
+ },
+ {
+ name: 'live',
+ icon: Live,
+ }
+ ];
+
+ return (
+
+ {tabIcons.map(({ name, icon }, index) => {
+ return (
+
+ );
+ })}
+
+ );
+}
+
+export default TabBar;
diff --git a/cocode/src/containers/Live/TabBar/live.svg b/cocode/src/containers/Live/TabBar/live.svg
new file mode 100644
index 00000000..79bd94bb
--- /dev/null
+++ b/cocode/src/containers/Live/TabBar/live.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/cocode/src/containers/Live/TabBar/style.js b/cocode/src/containers/Live/TabBar/style.js
new file mode 100644
index 00000000..f1aa8450
--- /dev/null
+++ b/cocode/src/containers/Live/TabBar/style.js
@@ -0,0 +1,15 @@
+import styled from 'styled-components';
+
+const TabBar = styled.nav`
+ & {
+ min-width: 4rem;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+
+ background-color: ${({ tabBarBGColor }) => tabBarBGColor};
+ }
+`;
+
+export { TabBar };
diff --git a/cocode/src/containers/Live/TabContainer/index.js b/cocode/src/containers/Live/TabContainer/index.js
new file mode 100644
index 00000000..963ee87a
--- /dev/null
+++ b/cocode/src/containers/Live/TabContainer/index.js
@@ -0,0 +1,25 @@
+import React, { useEffect, useContext } from 'react';
+import * as Styled from './style';
+
+import { ProjectContext } from 'contexts';
+import ExplorerTab from '../ExplorerTab';
+import LiveTab from '../LiveTab';
+
+function TabContainer() {
+ const { clickedTabIndex } = useContext(ProjectContext);
+
+ const tapMapping = {
+ 0: ,
+ 1:
+ };
+
+ const renderTab = () => tapMapping[clickedTabIndex];
+
+ useEffect(() => {
+ renderTab();
+ }, [clickedTabIndex]);
+
+ return {renderTab()};
+}
+
+export default TabContainer;
diff --git a/cocode/src/containers/Live/TabContainer/style.js b/cocode/src/containers/Live/TabContainer/style.js
new file mode 100644
index 00000000..1c75d251
--- /dev/null
+++ b/cocode/src/containers/Live/TabContainer/style.js
@@ -0,0 +1,10 @@
+import styled from 'styled-components';
+import { TAB_CONTAINER_THEME } from 'constants/theme';
+
+const Container = styled.section`
+ & {
+ background-color: ${TAB_CONTAINER_THEME.tabContainerBGColor};
+ }
+`;
+
+export { Container };
diff --git a/cocode/src/containers/Project/DependencyTab/index.js b/cocode/src/containers/Project/DependencyTab/index.js
index d027436c..ec529a8d 100644
--- a/cocode/src/containers/Project/DependencyTab/index.js
+++ b/cocode/src/containers/Project/DependencyTab/index.js
@@ -6,7 +6,7 @@ import Dependency from 'components/Project/Dependency';
import DependencyNow from 'components/Project/DependencyNow';
import DependencySearch from 'components/Project/DependencySearch';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
const TabTitleFirst = 'DEPENDENCIES';
const TabTitleSecond = 'SEARCH DEPENDENCY';
diff --git a/cocode/src/containers/Project/Editor/index.js b/cocode/src/containers/Project/Editor/index.js
index 89839b54..c0b1b959 100644
--- a/cocode/src/containers/Project/Editor/index.js
+++ b/cocode/src/containers/Project/Editor/index.js
@@ -5,7 +5,7 @@ import * as Styled from './style';
import FileTabBar from 'components/Project/FileTabBar';
import MonacoEditor from 'components/Project/MonacoEditor';
-import { UserContext, ProjectContext } from 'contexts';
+import { ProjectContext } from 'contexts';
import {
updateCodeActionCreator,
saveFileActionCreator
@@ -20,10 +20,11 @@ import { isPressCtrlAndS } from 'utils/keyDownEvent';
let timer;
const DEBOUNCING_TIME = 800;
-function Editor({ handleForkCoconut }) {
- const { user } = useContext(UserContext);
+function Editor() {
const { projectId } = useParams();
- const { project, dispatchProject } = useContext(ProjectContext);
+ const { project, dispatchProject, forkCoconut } = useContext(
+ ProjectContext
+ );
const [code, setCode] = useState(project.editingCode);
const [isEditorMounted, setIsEditorMounted] = useState(false);
const [_, setRequest] = useFetch({});
@@ -50,8 +51,6 @@ function Editor({ handleForkCoconut }) {
setRequest(updateFileAPI);
};
- const isNotMyProject = !user || user.username !== project.author;
-
const handleOnKeyDown = e => {
if (!isPressCtrlAndS(e)) return;
@@ -59,10 +58,8 @@ function Editor({ handleForkCoconut }) {
const { files, selectedFileId } = project;
if (!files[selectedFileId].isEditing) return;
- if (isNotMyProject) {
- handleForkCoconut();
- return;
- }
+ const isProgress = forkCoconut({});
+ if (isProgress) return;
handleRequestUpdateCode();
dispatchProject(saveFileActionCreator());
diff --git a/cocode/src/containers/Project/ExplorerTab/index.js b/cocode/src/containers/Project/ExplorerTab/index.js
index 016fc38b..a3afa388 100644
--- a/cocode/src/containers/Project/ExplorerTab/index.js
+++ b/cocode/src/containers/Project/ExplorerTab/index.js
@@ -8,7 +8,7 @@ import {
import Directory from 'components/Project/Directory';
import NewFile from 'components/Project/NewFile';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import {
selectFileActionCreator,
updateFileNameActionCreator,
@@ -16,7 +16,7 @@ import {
moveFileActionCreator
} from 'actions/Project';
-const TAB_TITLE = 'EXPLOLER';
+const TAB_TITLE = 'EXPLORER';
function TabHeader({ handleCreateFile }) {
return (
diff --git a/cocode/src/containers/Project/InfoTab/index.js b/cocode/src/containers/Project/InfoTab/index.js
index 2fcab4b0..a35980c0 100644
--- a/cocode/src/containers/Project/InfoTab/index.js
+++ b/cocode/src/containers/Project/InfoTab/index.js
@@ -4,7 +4,7 @@ import * as Styled from './style';
import Modify from './modify.svg';
import { KEY_CODE_ENTER } from 'constants/keyCode';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import { updateProjectInfoActionCreator } from 'actions/Project';
import useFetch from 'hooks/useFetch';
import {
@@ -29,6 +29,7 @@ function Info({
setRequest,
dispatchProject
}) {
+ const { forkCoconut } = useContext(ProjectContext);
const input = useRef();
const [isEditable, setIsEditable] = useState(false);
const [value, setValue] = useState(content);
@@ -44,6 +45,12 @@ function Info({
const newContent = event.currentTarget.textContent;
if (content === newContent) return;
setValue(event.currentTarget.textContent);
+
+ const isProgress = forkCoconut({
+ info: { [title]: newContent }
+ });
+ if (isProgress) return;
+
setRequest(
updateCoconutsAPICreator(projectId, { [title]: newContent })
);
diff --git a/cocode/src/containers/Project/LiveOffTab/index.js b/cocode/src/containers/Project/LiveOffTab/index.js
new file mode 100644
index 00000000..2aeb4341
--- /dev/null
+++ b/cocode/src/containers/Project/LiveOffTab/index.js
@@ -0,0 +1,32 @@
+import React, { useContext } from 'react';
+import { useParams, useHistory } from 'react-router-dom';
+import * as Styled from './style';
+import { ProjectContext } from 'contexts';
+
+const OFF_BUTTON_LABEL = 'Go Live';
+const OFF_DESCRIPTION =
+ 'Invite others to live edit this coconut with you. We’re doing it live!';
+
+function LiveOffTab() {
+ const history = useHistory();
+ const { projectId } = useParams();
+ const { forkCoconut } = useContext(ProjectContext);
+ const handleConnectSocket = () => {
+ const idOfNewProject = forkCoconut({ live: true });
+ if (idOfNewProject) return;
+
+ history.replace(`../live/${projectId}`);
+ };
+
+ return (
+
+ {OFF_DESCRIPTION}
+
+
+ {OFF_BUTTON_LABEL}
+
+
+ );
+}
+
+export default LiveOffTab;
diff --git a/cocode/src/containers/Project/LiveOffTab/style.js b/cocode/src/containers/Project/LiveOffTab/style.js
new file mode 100644
index 00000000..5cb0fa7a
--- /dev/null
+++ b/cocode/src/containers/Project/LiveOffTab/style.js
@@ -0,0 +1,52 @@
+import styled from 'styled-components';
+import { LIVE_TAB_THEME } from 'constants/theme';
+
+const Container = styled.div`
+ & {
+ margin: 0.7rem 1rem;
+ }
+`;
+
+const Description = styled.div`
+ & {
+ color: ${LIVE_TAB_THEME.liveFontColor};
+ font-size: 1rem;
+ font-weight: 100;
+ margin-bottom: 1rem;
+ }
+`;
+
+const Button = styled.button`
+ & {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: -webkit-fill-available;
+ padding: 0.7rem 2.2rem;
+ border-radius: 0.7rem;
+ background-color: ${LIVE_TAB_THEME.liveButtonBGColor};
+ font-size: 1rem;
+ font-weight: 400;
+ }
+
+ &:hover {
+ background-color: ${LIVE_TAB_THEME.liveButtonBGColorHover};
+ }
+`;
+
+const Circle = styled.div`
+ & {
+ width: 0.5rem;
+ height: 0.5rem;
+ margin-right: 0.5rem;
+ background-color: ${LIVE_TAB_THEME.liveCircleBGColor};
+ border-radius: 50%;
+ }
+`;
+
+export {
+ Container,
+ Description,
+ Button,
+ Circle
+};
diff --git a/cocode/src/containers/Project/LiveTab/LiveOff.js b/cocode/src/containers/Project/LiveTab/LiveOff.js
deleted file mode 100644
index 36732394..00000000
--- a/cocode/src/containers/Project/LiveTab/LiveOff.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as Styled from './style';
-import React from 'react';
-
-const OFF_BUTTON_LABEL = 'Go Live';
-const OFF_DESCRIPTION =
- 'Invite others to live edit this coconut with you. We’re doing it live!';
-
-function LiveOff({ onClick }) {
- return (
- <>
- {OFF_DESCRIPTION}
- {OFF_BUTTON_LABEL}
- >
- );
-}
-
-export default LiveOff;
diff --git a/cocode/src/containers/Project/LiveTab/LiveOn.js b/cocode/src/containers/Project/LiveTab/LiveOn.js
deleted file mode 100644
index 3e0e03ac..00000000
--- a/cocode/src/containers/Project/LiveTab/LiveOn.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React, { useContext } from 'react';
-import { LiveContext } from 'contexts';
-import * as Styled from './style';
-import LiveUserProfile from 'components/Project/LiveUserProfile';
-
-const LIVE_STATUS_LABEL = 'You’ve gone live!';
-const ON_BUTTON_LABEL = 'Stop Live';
-const ON_DESCRIPTION =
- 'Share this link with others to invite them to the live.';
-
-function LiveOn({ onClick }) {
- const { url, participants, owner } = useContext(LiveContext);
-
- return (
- <>
-
-
- {LIVE_STATUS_LABEL}
-
- {ON_DESCRIPTION}
- {url}
- {ON_BUTTON_LABEL}
-
- >
- );
-}
-
-export default LiveOn;
diff --git a/cocode/src/containers/Project/LiveTab/index.js b/cocode/src/containers/Project/LiveTab/index.js
index d58cc615..113b97b0 100644
--- a/cocode/src/containers/Project/LiveTab/index.js
+++ b/cocode/src/containers/Project/LiveTab/index.js
@@ -1,53 +1,17 @@
-import React, { useContext } from 'react';
+import React from 'react';
import * as Styled from './style';
-import { LiveContext } from 'contexts';
-import LiveOn from './LiveOn';
-import LiveOff from './LiveOff';
-import { liveOffActionCreator, fetchLiveActionCreator } from 'actions/Live';
-import avatar from 'components/Common/UserProfile/avatar.jpeg';
+import LiveOffTab from 'containers/Project/LiveOffTab';
-const TAB_TATILE = 'LIVE';
-
-const dummy = {
- url: 'https://cocode.com/live/',
- participants: [
- {
- username: 'basiltoast',
- avatar
- },
- {
- username: 'basiltoast',
- avatar
- },
- {
- username: 'basiltoast',
- avatar
- }
- ],
- owner: {
- username: 'lallaheeee',
- avatar
- }
-};
+const TAB_TITLE = 'LIVE';
function LiveTab() {
- const { url, dispatchLive } = useContext(LiveContext);
-
- const handleTurnLive = () => {
- if (url) dispatchLive(liveOffActionCreator());
- else dispatchLive(fetchLiveActionCreator(dummy));
- };
return (
<>
- {TAB_TATILE}
-
- {url ? (
-
- ) : (
-
- )}
-
+ {TAB_TITLE}
+
+
+
>
);
}
diff --git a/cocode/src/containers/Project/LiveTab/style.js b/cocode/src/containers/Project/LiveTab/style.js
index 67666437..d2378806 100644
--- a/cocode/src/containers/Project/LiveTab/style.js
+++ b/cocode/src/containers/Project/LiveTab/style.js
@@ -1,15 +1,5 @@
import styled from 'styled-components';
-import { TAB_CONTAINER_THEME, LIVE_TAB_THEME } from 'constants/theme';
-
-const Wrapper = styled.div`
- & {
- padding: 0.7rem 1rem;
- }
-
- & > * {
- padding: 0.5rem 0;
- }
-`;
+import { TAB_CONTAINER_THEME } from 'constants/theme';
const Title = styled.h1`
& {
@@ -21,64 +11,6 @@ const Title = styled.h1`
}
`;
-const Description = styled.div`
- & {
- color: ${LIVE_TAB_THEME.liveFontColor};
- font-size: 1rem;
- font-weight: 100;
- }
-`;
-
-const Button = styled.button`
- & {
- width: -webkit-fill-available;
- margin: 1rem 0;
- padding: 1rem 2.2rem;
- border-radius: 0.3rem;
- background-color: ${LIVE_TAB_THEME.liveButtonBGColor};
- font-size: 1rem;
- font-weight: 400;
- }
-
- &:hover {
- background-color: ${LIVE_TAB_THEME.liveButtonBGColorHover};
- }
-`;
-
-const LiveStatusLabel = styled.div`
- & {
- display: flex;
- color: ${LIVE_TAB_THEME.liveStatusLabelColor};
- font-size: 1.1rem;
- font-weight: lighter;
- }
-`;
-
-const LiveStatusSpan = styled.span`
- & {
- background-color: ${LIVE_TAB_THEME.liveStatusLabelColor};
- margin: auto 1rem auto 0.3rem;
- width: 0.6rem;
- height: 0.6rem;
- border-radius: 50%;
- }
-`;
-
-const LinkURL = styled.div`
- & {
- background-color: ${LIVE_TAB_THEME.liveLinkBGColor};
- color: ${LIVE_TAB_THEME.liveFontColor};
- font-size: 0.9rem;
- padding-left: 1rem;
- }
-`;
-
export {
- Wrapper,
- Title,
- Description,
- Button,
- LiveStatusLabel,
- LiveStatusSpan,
- LinkURL
+ Title
};
diff --git a/cocode/src/containers/Project/TabBar/index.js b/cocode/src/containers/Project/TabBar/index.js
index ad6e3d1f..aeb86c43 100644
--- a/cocode/src/containers/Project/TabBar/index.js
+++ b/cocode/src/containers/Project/TabBar/index.js
@@ -1,7 +1,7 @@
import React, { useContext } from 'react';
import * as Styled from './style';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import TabIcon from 'components/Project/TabIcon';
diff --git a/cocode/src/containers/Project/TabContainer/index.js b/cocode/src/containers/Project/TabContainer/index.js
index de4b2249..d54c8e2f 100644
--- a/cocode/src/containers/Project/TabContainer/index.js
+++ b/cocode/src/containers/Project/TabContainer/index.js
@@ -1,7 +1,7 @@
import React, { useEffect, useContext } from 'react';
import * as Styled from './style';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectContext } from 'contexts';
import InfoTab from '../InfoTab';
import ExplorerTab from '../ExplorerTab';
diff --git a/cocode/src/containers/SignIn/index.js b/cocode/src/containers/SignIn/index.js
new file mode 100644
index 00000000..b67a4a46
--- /dev/null
+++ b/cocode/src/containers/SignIn/index.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import * as Styled from './style';
+import Github from 'components/Common/LoginModalBody/github.svg';
+import { API } from 'config';
+
+const SIGN_IN_TITLE = 'Sorry, This service requires a login.';
+
+function SignIn() {
+ const handleClickLoginButton = () => (window.location.href = API.login);
+
+ return (
+
+ {SIGN_IN_TITLE}
+
+
+ Sign In With GitHub
+
+
+ );
+}
+
+export default SignIn;
diff --git a/cocode/src/containers/SignIn/style.js b/cocode/src/containers/SignIn/style.js
new file mode 100644
index 00000000..cabf42fd
--- /dev/null
+++ b/cocode/src/containers/SignIn/style.js
@@ -0,0 +1,50 @@
+import styled from 'styled-components';
+import { SIGN_IN_THEME } from 'constants/theme';
+
+const Logo = styled.img`
+ height: 1.2rem;
+ margin-right: 0.8rem;
+ filter: invert(1);
+`;
+
+const Wrapper = styled.div`
+ & {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ height: 70vh;
+ padding: 5rem;
+ }
+`;
+
+const Title = styled.h1`
+ & {
+ text-align: center;
+ font-size: 3rem;
+ font-weight: 100;
+ }
+`;
+
+const LoginButton = styled.button`
+ & {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 2rem 0 1.5rem 0;
+ padding: 1rem 2.5rem;
+
+ font-size: 1.5rem;
+
+ background-color: ${SIGN_IN_THEME.signInButtonBGColor};
+ color: ${SIGN_IN_THEME.signInButtonTextColor};
+ border-radius: 0.5rem;
+ }
+
+ &:hover {
+ background-color: ${SIGN_IN_THEME.signInButtonBGHoverColor};
+ }
+`;
+
+export { Logo, Wrapper, LoginButton, Title };
diff --git a/cocode/src/hooks/useFetch.js b/cocode/src/hooks/useFetch.js
index ad3522da..22889a38 100644
--- a/cocode/src/hooks/useFetch.js
+++ b/cocode/src/hooks/useFetch.js
@@ -1,7 +1,7 @@
import { useState, useEffect, useReducer } from 'react';
import axios from 'axios';
import { DEFAULT_REQUEST_OPTION } from 'config';
-import APIReducer from 'reducers/APIReducer';
+import { APIReducer } from 'reducers';
import {
fetchReadyActionCreator,
fetchLoadActionCreator,
diff --git a/cocode/src/pages/DashBoard/index.js b/cocode/src/pages/DashBoard/index.js
index 3b0cd468..6ddcacd3 100644
--- a/cocode/src/pages/DashBoard/index.js
+++ b/cocode/src/pages/DashBoard/index.js
@@ -2,24 +2,15 @@ import React, { useContext, useEffect, useReducer } from 'react';
import { useHistory } from 'react-router-dom';
import ProjectCardList from 'containers/DashBoard/ProjectCardList';
import Header from 'containers/Common/Header';
-import CoconutSpinner from 'components/Common/CoconutSpinner';
-import * as Styled from './style';
+import LoadingSpinner from 'containers/Common/LoadingSpinner';
import { UserContext, DashBoardContext } from 'contexts';
-import DashBoardReducer from 'reducers/DashboardReducer';
+import { DashBoardReducer } from 'reducers';
import useFetch from 'hooks/useFetch';
import { getCoconutsAPICreator } from 'apis/DashBoard';
import { fetchCoconutActionCreator } from 'actions/Dashboard';
import { LOADING_DASHBOARD } from 'constants/notificationMessage';
-
-function LoadingSpinner() {
- return (
-
-
- {LOADING_DASHBOARD}
-
- );
-}
+import { getCookie } from 'utils/controlCookie';
function DashBoard() {
const { user } = useContext(UserContext);
@@ -34,10 +25,15 @@ function DashBoard() {
data && dispatchDashboard(fetchCoconutActionCreator(data));
};
+ if (!getCookie('jwt')) {
+ localStorage.setItem('redirectURL', window.location.href);
+ history.replace('../signin');
+ }
+
useEffect(handleRequestGetCoconutAPI, [user]);
useEffect(handleSetDashBoardState, [data]);
- if (loading) return ;
+ if (loading) return ;
if (error) history.push('/weAreSorry');
return (
diff --git a/cocode/src/pages/Empty/index.js b/cocode/src/pages/Empty/index.js
new file mode 100644
index 00000000..4aa19349
--- /dev/null
+++ b/cocode/src/pages/Empty/index.js
@@ -0,0 +1,18 @@
+import React, { useEffect } from 'react';
+import Header from 'containers/Common/Header';
+
+function Empty() {
+ useEffect(() => {
+ const redirectURL = localStorage.getItem('redirectURL');
+ if (redirectURL) {
+ window.location.href = redirectURL;
+ localStorage.removeItem('redirectURL');
+ }
+ }, []);
+
+ return (
+
+ );
+}
+
+export default Empty;
\ No newline at end of file
diff --git a/cocode/src/pages/History/index.js b/cocode/src/pages/History/index.js
deleted file mode 100644
index 246ca545..00000000
--- a/cocode/src/pages/History/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { Route } from 'react-router-dom';
-
-import { Version1 } from 'pages';
-import Header from 'containers/Common/Header';
-import CocodeHistory from 'containers/History/CocodeHistory';
-
-function HistoryHome() {
- return ;
-}
-
-function History({ match }) {
- return (
- <>
-
-
-
- >
- );
-}
-
-export default History;
diff --git a/cocode/src/pages/Live/index.js b/cocode/src/pages/Live/index.js
new file mode 100644
index 00000000..f5d490da
--- /dev/null
+++ b/cocode/src/pages/Live/index.js
@@ -0,0 +1,170 @@
+import React, {
+ useReducer,
+ useEffect,
+ useState,
+ useContext,
+ useCallback
+} from 'react';
+import { useHistory, useParams } from 'react-router-dom';
+import * as Styled from './style';
+import io from 'socket.io-client';
+
+import Header from 'containers/Common/Header';
+import TabBar from 'containers/Live/TabBar';
+import TabContainer from 'containers/Live/TabContainer';
+import Editor from 'containers/Live/Editor';
+import LoadingSpinner from 'containers/Common/LoadingSpinner';
+import BrowserV2 from 'components/Project/BrowserV2';
+import { SplitPaneContainer } from 'components/Common/SplitPane';
+import addToast from 'components/Common/Toast';
+
+import { LiveContext, ProjectContext, UserContext } from 'contexts';
+import { ProjectReducer } from 'reducers';
+import { fetchProjectActionCreator } from 'actions/Project';
+import {
+ liveOnActionCreator,
+ liveOffActionCreator,
+ liveJoinUserActionCreator,
+ liveLeaveUserActionCreator
+} from 'actions/Live';
+
+import { TAB_BAR_THEME } from 'constants/theme';
+import { COCODE_SERVER } from 'config';
+import useFetch from 'hooks/useFetch';
+import { getProjectInfoAPICreator } from 'apis/Project';
+import { SHUT_DOWN_LIVE_SHARE, LOADING_LIVE } from 'constants/notificationMessage';
+import { getCookie } from 'utils/controlCookie';
+
+const DEFAULT_CLICKED_TAB_INDEX = 0;
+let socket;
+
+function Live() {
+ const history = useHistory();
+ const { projectId } = useParams();
+ const { user } = useContext(UserContext);
+ const { liveServer, dispatchLive } = useContext(LiveContext);
+ const [{ data, loading, error }, setRequest] = useFetch({});
+ const [isFetched, setIsFetched] = useState(false);
+ const [isConnected, setIsConnected] = useState(false);
+ const [clickedTabIndex, setClickedTabIndex] = useState(
+ DEFAULT_CLICKED_TAB_INDEX
+ );
+ const [project, dispatchProject] = useReducer(ProjectReducer, {});
+
+ if (!getCookie('jwt')) {
+ localStorage.setItem('redirectURL', window.location.href);
+ history.replace('../signin');
+ }
+
+ const handleFetchProject = () => {
+ const getProjectInfoAPI = getProjectInfoAPICreator(projectId);
+ setRequest(getProjectInfoAPI);
+ };
+
+ const handleSetProjectState = project => {
+ const fetchProjectAction = fetchProjectActionCreator({ project });
+ dispatchProject(fetchProjectAction);
+ setIsFetched(true);
+ };
+
+ const handleSetProject = () => {
+ if (!data) return;
+ if (!isFetched) handleSetProjectState(data);
+ };
+
+ const handleConnected = () => {
+ socket.emit('createRoom', { user, projectId, project });
+ };
+
+ const handleAlreadyExistRoom = ({ host, project, participants }) => {
+ dispatchLive(
+ liveOnActionCreator({
+ socket,
+ url: `${COCODE_SERVER}/live/${projectId}`,
+ owner: host,
+ project,
+ participants
+ })
+ );
+ };
+
+ const handleSuccessCreatedRoom = ({ project, participants }) => {
+ dispatchLive(
+ liveOnActionCreator({
+ socket,
+ url: `${COCODE_SERVER}/live/${projectId}`,
+ owner: user,
+ project,
+ participants
+ })
+ );
+ };
+
+ const handleJoinUser = ({ participants }) => {
+ dispatchLive(
+ liveJoinUserActionCreator({
+ participants
+ })
+ );
+ };
+
+ const handleLeaveUser = ({ participants }) => {
+ dispatchLive(
+ liveLeaveUserActionCreator({
+ participants
+ })
+ );
+ };
+
+ const handleCloseSocket = () => {
+ socket.close();
+ dispatchLive(liveOffActionCreator());
+ addToast.error(SHUT_DOWN_LIVE_SHARE);
+ };
+
+ const handleConnectSocket = useCallback(() => {
+ if (!Object.keys(project).length || isConnected || !user) return;
+ setIsConnected(true);
+ socket = io(liveServer);
+ socket.on('connected', handleConnected);
+ socket.on('alreadyExistRoom', handleAlreadyExistRoom);
+ socket.on('successCreatedRoom', handleSuccessCreatedRoom);
+ socket.on('joinUser', handleJoinUser);
+ socket.on('leaveUser', handleLeaveUser);
+ socket.on('close', handleCloseSocket);
+ }, [project]);
+
+ useEffect(handleFetchProject, []);
+ useEffect(handleConnectSocket, [project]);
+ useEffect(handleSetProject, [data, isFetched]);
+
+ if (loading) return ;
+ if (error) history.push('/weAreSorry');
+
+ return (
+
+
+ {isFetched && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+export default Live;
diff --git a/cocode/src/pages/Version1/style.js b/cocode/src/pages/Live/style.js
similarity index 60%
rename from cocode/src/pages/Version1/style.js
rename to cocode/src/pages/Live/style.js
index 7689cf6c..2a84f910 100644
--- a/cocode/src/pages/Version1/style.js
+++ b/cocode/src/pages/Live/style.js
@@ -5,11 +5,11 @@ const Main = styled.main`
display: flex;
flex-direction: row;
- height: ${({ theme }) => theme.exceptHeaderHeight};
- }
+ height: 91vh;
- .Stretch-item {
- width: 100%;
+ .Project-main-stretch {
+ flex-grow: 2;
+ }
}
`;
diff --git a/cocode/src/pages/Project/index.js b/cocode/src/pages/Project/index.js
index a7c3fcd6..ce1ea936 100644
--- a/cocode/src/pages/Project/index.js
+++ b/cocode/src/pages/Project/index.js
@@ -6,46 +6,67 @@ import Header from 'containers/Common/Header';
import TabBar from 'containers/Project/TabBar';
import TabContainer from 'containers/Project/TabContainer';
import Editor from 'containers/Project/Editor';
+import LoadingSpinner from 'containers/Common/LoadingSpinner';
import BrowserV2 from 'components/Project/BrowserV2';
import { SplitPaneContainer } from 'components/Common/SplitPane';
import addToast from 'components/Common/Toast';
-import ProjectReducer from 'reducers/ProjectReducer';
-import ProjectContext from 'contexts/ProjectContext';
+import { ProjectReducer } from 'reducers';
+import { ProjectContext, UserContext } from 'contexts';
import { fetchProjectActionCreator } from 'actions/Project';
import { TAB_BAR_THEME } from 'constants/theme';
-import UserContext from 'contexts/UserContext';
import useFetch from 'hooks/useFetch';
import { reactTemplate } from 'template/react';
import copyProject from 'template/copyProject';
import { getProjectInfoAPICreator, forkProjectAPICreator } from 'apis/Project';
-import { LiveStore } from 'stores';
import parseProject from 'pages/Project/parseProject';
import { CREATED, CONFLICT } from 'constants/statusCode';
+import {
+ SUCCESS_FORK,
+ CONFLICT_FORK,
+ LOADING_PROJECT
+} from 'constants/notificationMessage';
const DEFAULT_CLICKED_TAB_INDEX = 0;
function Project() {
const { user } = useContext(UserContext);
- const history = useHistory();
const { projectId } = useParams();
+ const history = useHistory();
const [{ data, loading, error, status }, setRequest] = useFetch({});
+ const [isLive, setIsLive] = useState(false);
const [isFetched, setIsFetched] = useState(false);
const [clickedTabIndex, setClickedTabIndex] = useState(
DEFAULT_CLICKED_TAB_INDEX
);
const [project, dispatchProject] = useReducer(ProjectReducer, {});
+ const isNotMyProject = !user || user.username !== project.author;
- const handleForkCoconut = () => {
- const username = user ? user.username : 'anonymous';
- const parsedProject = parseProject(project, username);
+ const forkCoconut = ({ live, info }) => {
+ if (!isNotMyProject) return false;
+
+ const parsedProject = preTreatBeforeFork({ live, info });
const forkProjectInfoAPI = forkProjectAPICreator(parsedProject);
setRequest(forkProjectInfoAPI);
handleSetProjectState(parsedProject);
- return project;
+ return parsedProject._id;
+ };
+
+ const preTreatBeforeFork = ({ live, info }) => {
+ if (live) setIsLive(true);
+
+ const username = user ? user.username : 'anonymous';
+ let parsedProject = parseProject(project, username);
+
+ if (info) {
+ Object.entries(info).forEach(([title, value]) => {
+ parsedProject[title] = value;
+ });
+ }
+ return parsedProject;
};
const handleFetchProject = () => {
@@ -66,17 +87,13 @@ function Project() {
};
const handleChangeHistoryAtForked = () => {
- if (status === CONFLICT) {
- addToast.error('already forked! enjoy Coconut ');
- }
-
+ if (status === CONFLICT) addToast.error(CONFLICT_FORK);
if (status !== CREATED) return;
- projectId !== 'new'
- ? history.push(`../project/${data._id}`)
- : history.replace(`../project/${data._id}`);
+ const url = isLive ? `../live/${data._id}` : `../project/${data._id}`;
+ projectId !== 'new' ? history.push(url) : history.replace(url);
- addToast.info('Forked Coconut, Success !');
+ addToast.info(SUCCESS_FORK);
};
const handleSetProject = () => {
@@ -91,9 +108,8 @@ function Project() {
useEffect(handleChangeHistoryAtForked, [status]);
- // //TODO loading 컴포넌트 만들기
- if (loading) return Loading...
;
- if (error) return 다시 시도해주세요.
;
+ if (loading) return ;
+ if (error) history.push('/weAreSorry');
return (
-
-
- {isFetched && (
-
-
-
-
-
-
-
-
+
+ {isFetched && (
+
+
+
+
+
+
+
-
- )}
-
+
+
+ )}
);
}
diff --git a/cocode/src/pages/Project/style.js b/cocode/src/pages/Project/style.js
index f290ce40..2a84f910 100644
--- a/cocode/src/pages/Project/style.js
+++ b/cocode/src/pages/Project/style.js
@@ -5,7 +5,7 @@ const Main = styled.main`
display: flex;
flex-direction: row;
- height: 88vh;
+ height: 91vh;
.Project-main-stretch {
flex-grow: 2;
diff --git a/cocode/src/pages/SignIn/index.js b/cocode/src/pages/SignIn/index.js
new file mode 100644
index 00000000..cc65d251
--- /dev/null
+++ b/cocode/src/pages/SignIn/index.js
@@ -0,0 +1,21 @@
+import React, { useContext } from 'react';
+import { useHistory } from 'react-router-dom';
+import Header from 'containers/Common/Header';
+import SignInContainer from 'containers/SignIn';
+import { UserContext } from 'contexts';
+
+function SignIn() {
+ const { user } = useContext(UserContext);
+ const history = useHistory();
+
+ if (user) history.replace('../');
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export default SignIn;
diff --git a/cocode/src/pages/Version1/index.js b/cocode/src/pages/Version1/index.js
deleted file mode 100644
index 852be306..00000000
--- a/cocode/src/pages/Version1/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useReducer, useEffect } from 'react';
-import * as Styled from './style';
-
-import MonacoEditor from 'components/Project/MonacoEditor';
-import BrowserV1 from 'components/Project/BrowserV1';
-import { SplitPaneContainer } from 'components/Common/SplitPane';
-
-import ProjectReducer from 'reducers/ProjectReducer';
-
-import reactTemplate from 'template/copyProject';
-
-import {
- fetchProjectActionCreator,
- updateCodeActionCreator
-} from 'actions/Project';
-
-function Version1() {
- const [project, dispatchProject] = useReducer(ProjectReducer, {});
-
- const handleFetchProject = () => {
- const fetchProjectAction = fetchProjectActionCreator({
- project: reactTemplate()
- });
- dispatchProject(fetchProjectAction);
- };
-
- const handleChangeCode = (_, changedCode) => {
- const updateCodeAction = updateCodeActionCreator(changedCode);
- dispatchProject(updateCodeAction);
- };
-
- useEffect(handleFetchProject, []);
-
- return (
-
-
-
-
-
-
- );
-}
-
-export default Version1;
diff --git a/cocode/src/pages/index.js b/cocode/src/pages/index.js
index a9f7f95e..ea53e9b0 100644
--- a/cocode/src/pages/index.js
+++ b/cocode/src/pages/index.js
@@ -1,8 +1,9 @@
import Home from './Home';
import DashBoard from './DashBoard';
import Project from './Project';
-import History from './History';
-import Version1 from './Version1';
import NotFound from './NotFound';
+import Live from './Live';
+import SignIn from './SignIn';
+import Empty from './Empty';
-export { Home, DashBoard, Project, History, Version1, NotFound };
+export { Home, DashBoard, Project, NotFound, Live, SignIn, Empty };
diff --git a/cocode/src/reducers/DashboardReducer.js b/cocode/src/reducers/DashBoardReducer.js
similarity index 100%
rename from cocode/src/reducers/DashboardReducer.js
rename to cocode/src/reducers/DashBoardReducer.js
diff --git a/cocode/src/reducers/LiveReducer.js b/cocode/src/reducers/LiveReducer.js
index dbc21e3a..0509c9ab 100644
--- a/cocode/src/reducers/LiveReducer.js
+++ b/cocode/src/reducers/LiveReducer.js
@@ -1,49 +1,44 @@
import {
- FETCH_LIVE,
LIVE_ON,
LIVE_OFF,
LIVE_JOIN_USER,
- LIVE_LEFT_USER
+ LIVE_LEAVE_USER
} from 'actions/types';
-const fetchLive = (state, { url, participants, owner }) => ({
+const liveOn = (state, { url, socket, project, owner }) => {
+ return ({
+ ...state,
+ url,
+ socket,
+ project,
+ owner,
+ participants: []
+ });
+};
+
+const liveOff = (state) => ({
...state,
- url,
- participants,
- owner
-});
-
-const liveOn = (state, { owner }) => ({
- ...state,
- owner,
- participants: []
-});
-
-const liveOff = () => ({
- url: null,
+ socket: null,
owner: undefined,
participants: []
});
-const joinUser = (state, { joinUser }) => ({
+const joinUser = (state, { participants }) => ({
...state,
- participants: [...state.participants, joinUser]
+ participants,
});
-const leftUser = (state, { leftUser }) => ({
+const leaveUser = (state, { participants }) => ({
...state,
- participants: state.participants.filter(
- ({ username }) => username !== leftUser.username
- )
+ participants,
});
function LiveReducer(state, { type, payload }) {
const reducers = {
- [FETCH_LIVE]: fetchLive,
[LIVE_ON]: liveOn,
[LIVE_OFF]: liveOff,
[LIVE_JOIN_USER]: joinUser,
- [LIVE_LEFT_USER]: leftUser
+ [LIVE_LEAVE_USER]: leaveUser
};
const reducer = reducers[type];
diff --git a/cocode/src/reducers/ProjectReducer.js b/cocode/src/reducers/ProjectReducer.js
index d36d9d81..dea3c481 100644
--- a/cocode/src/reducers/ProjectReducer.js
+++ b/cocode/src/reducers/ProjectReducer.js
@@ -2,6 +2,7 @@
import {
UPDATE_PROJECT_INFO,
UPDATE_CODE,
+ UPDATE_CODE_FROM_FILE_ID,
FETCH_PROJECT,
SELECT_FILE,
CREATE_FILE,
@@ -109,6 +110,20 @@ const updateCode = (state, { changedCode }) => {
};
};
+const updateCodeFromFileId = (state, { fileId, changedCode }) => {
+ return {
+ ...state,
+ files: {
+ ...state.files,
+ [fileId]: {
+ ...state.files[fileId],
+ contents: changedCode,
+ isEditing: true
+ }
+ }
+ };
+};
+
// Select file
const selectFile = (state, { selectedFileId }) => {
return {
@@ -314,6 +329,7 @@ function ProjectReducer(state, { type, payload }) {
[UPDATE_PROJECT_INFO]: updateProjectInfo,
[FETCH_PROJECT]: fetchProject,
[UPDATE_CODE]: updateCode,
+ [UPDATE_CODE_FROM_FILE_ID]: updateCodeFromFileId,
[SELECT_FILE]: selectFile,
[UPDATE_FILE_NAME]: updateFileName,
[CREATE_FILE]: createFile,
diff --git a/cocode/src/reducers/index.js b/cocode/src/reducers/index.js
index e69de29b..e334352b 100644
--- a/cocode/src/reducers/index.js
+++ b/cocode/src/reducers/index.js
@@ -0,0 +1,6 @@
+import APIReducer from './APIReducer';
+import DashBoardReducer from './DashBoardReducer';
+import LiveReducer from './LiveReducer';
+import ProjectReducer from './ProjectReducer';
+
+export { APIReducer, DashBoardReducer, LiveReducer, ProjectReducer };
\ No newline at end of file
diff --git a/cocode/src/stores/LiveStore.js b/cocode/src/stores/LiveStore.js
index 7aeba0e4..d894ebda 100644
--- a/cocode/src/stores/LiveStore.js
+++ b/cocode/src/stores/LiveStore.js
@@ -1,20 +1,31 @@
import React, { useReducer } from 'react';
-import LiveReducer from 'reducers/LiveReducer';
+import { LiveReducer } from 'reducers';
import { LiveContext } from 'contexts';
+import { LIVE_SERVER } from 'config';
function LiveStore({ children }) {
- const [{ url, participants, owner }, dispatchLive] = useReducer(
- LiveReducer,
- {
- url: null,
- owner: undefined,
- participants: []
- }
- );
+ const initialValue = {
+ liveServer: LIVE_SERVER,
+ url: '',
+ project: {},
+ socket: null,
+ owner: undefined,
+ participants: []
+ };
+
+ const [live, dispatchLive] = useReducer(LiveReducer, initialValue);
+ const { liveServer, url, socket, participants, owner } = live;
return (
{children}
diff --git a/cocode/src/utils/controlCookie.js b/cocode/src/utils/controlCookie.js
new file mode 100644
index 00000000..a380f642
--- /dev/null
+++ b/cocode/src/utils/controlCookie.js
@@ -0,0 +1,13 @@
+import { DELETE_COOKIE_VALUE } from 'constants/cookie';
+
+function deleteCookie(key) {
+ document.cookie = key + DELETE_COOKIE_VALUE;
+}
+
+//참고: https://cofs.tistory.com/363
+function getCookie(name) {
+ const value = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
+ return value ? value[2] : null;
+}
+
+export { deleteCookie, getCookie };
diff --git a/cocode/src/utils/deleteCookie.js b/cocode/src/utils/deleteCookie.js
deleted file mode 100644
index ac26e1d5..00000000
--- a/cocode/src/utils/deleteCookie.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { DELETE_COOKIE_VALUE } from 'constants/cookie';
-
-function deleteCookie(key) {
- document.cookie = key + DELETE_COOKIE_VALUE;
- window.location.reload();
-};
-
-export default deleteCookie;
\ No newline at end of file
diff --git a/cocode/src/utils/domControl.js b/cocode/src/utils/domControl.js
index 13b6378e..e717734f 100644
--- a/cocode/src/utils/domControl.js
+++ b/cocode/src/utils/domControl.js
@@ -7,4 +7,12 @@ function changeDivEditable(node, status) {
if (status) node.focus();
}
-export { selectAllTextAboutFocusedDom, changeDivEditable };
+function copyToClipboard(node) {
+ const range = document.createRange();
+ range.selectNode(node);
+ window.getSelection().removeAllRanges();
+ window.getSelection().addRange(range);
+ document.execCommand('copy');
+}
+
+export { selectAllTextAboutFocusedDom, changeDivEditable, copyToClipboard };
diff --git a/cocode/src/utils/monacoWidget.js b/cocode/src/utils/monacoWidget.js
new file mode 100644
index 00000000..83bbe24a
--- /dev/null
+++ b/cocode/src/utils/monacoWidget.js
@@ -0,0 +1,46 @@
+class CursorWidget {
+ constructor(editor, userName, position) {
+ this.editor = editor;
+ this.id = userName;
+ this.domNode = null;
+ this.position = position;
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getDomNode() {
+ if (!this.domNode) {
+ this.domNode = document.createElement('div');
+ this.domNode.innerHTML = this.id;
+ this.domNode.style.background = 'grey';
+ this.domNode.id = this.id;
+ }
+ return this.domNode;
+ }
+
+ getPosition() {
+ return {
+ position: this.position,
+ preference: [0]
+ };
+ }
+ updatePosition(position) {
+ this.position = position;
+ this.editor.layoutContentWidget(this);
+ }
+
+ showCursor(position) {
+ this.domNode.style.visibility = 'inherit';
+ this.position = position;
+ this.editor.layoutContentWidget(this);
+ }
+
+ hiddenCursor() {
+ this.domNode.style.visibility = 'hidden';
+ this.editor.layoutContentWidget(this);
+ }
+}
+
+export { CursorWidget };
diff --git a/cocode/yarn.lock b/cocode/yarn.lock
index bebc62d7..24d05277 100644
--- a/cocode/yarn.lock
+++ b/cocode/yarn.lock
@@ -2505,6 +2505,11 @@ adjust-sourcemap-loader@2.0.0:
object-path "0.11.4"
regex-parser "2.2.10"
+after@0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
+ integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
+
aggregate-error@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
@@ -2792,6 +2797,11 @@ array.prototype.flatmap@^1.2.1:
es-abstract "^1.15.0"
function-bind "^1.1.1"
+arraybuffer.slice@~0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
+ integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
+
arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -3358,11 +3368,21 @@ babylon@^6.18.0:
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
+backo2@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
+ integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
+
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-arraybuffer@0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
+ integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
+
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
@@ -3398,6 +3418,13 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
+better-assert@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
+ integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
+ dependencies:
+ callsite "1.0.0"
+
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -3408,6 +3435,11 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
+blob@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
+ integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
+
bluebird@^3.3.5, bluebird@^3.5.5:
version "3.7.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
@@ -3710,6 +3742,11 @@ caller-path@^2.0.0:
dependencies:
caller-callsite "^2.0.0"
+callsite@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
+ integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
+
callsites@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
@@ -4119,11 +4156,26 @@ compare-func@^1.3.1:
array-ify "^1.0.0"
dot-prop "^3.0.0"
+component-bind@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
+ integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
+
+component-emitter@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+ integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
+
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+component-inherit@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
+ integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
+
compose-function@3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f"
@@ -4782,7 +4834,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.
dependencies:
ms "2.0.0"
-debug@=3.1.0:
+debug@=3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -4796,7 +4848,7 @@ debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
dependencies:
ms "^2.1.1"
-debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -5322,6 +5374,34 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
+engine.io-client@~3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.0.tgz#82a642b42862a9b3f7a188f41776b2deab643700"
+ integrity sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==
+ dependencies:
+ component-emitter "1.2.1"
+ component-inherit "0.0.3"
+ debug "~4.1.0"
+ engine.io-parser "~2.2.0"
+ has-cors "1.1.0"
+ indexof "0.0.1"
+ parseqs "0.0.5"
+ parseuri "0.0.5"
+ ws "~6.1.0"
+ xmlhttprequest-ssl "~1.5.4"
+ yeast "0.1.2"
+
+engine.io-parser@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed"
+ integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==
+ dependencies:
+ after "0.8.2"
+ arraybuffer.slice "~0.0.7"
+ base64-arraybuffer "0.1.5"
+ blob "0.0.5"
+ has-binary2 "~1.0.2"
+
enhanced-resolve@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
@@ -6680,6 +6760,18 @@ has-ansi@^2.0.0:
dependencies:
ansi-regex "^2.0.0"
+has-binary2@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
+ integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
+ dependencies:
+ isarray "2.0.1"
+
+has-cors@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
+ integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
+
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -7144,6 +7236,11 @@ indexes-of@^1.0.1:
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
+indexof@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
+ integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
+
infer-owner@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
@@ -7684,6 +7781,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+isarray@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+ integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -9570,6 +9672,11 @@ object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+object-component@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
+ integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
+
object-copy@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -9983,6 +10090,20 @@ parse5@5.1.0:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
+parseqs@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
+ integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
+ dependencies:
+ better-assert "~1.0.0"
+
+parseuri@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
+ integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
+ dependencies:
+ better-assert "~1.0.0"
+
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -12628,6 +12749,35 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
+socket.io-client@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
+ integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==
+ dependencies:
+ backo2 "1.0.2"
+ base64-arraybuffer "0.1.5"
+ component-bind "1.0.0"
+ component-emitter "1.2.1"
+ debug "~4.1.0"
+ engine.io-client "~3.4.0"
+ has-binary2 "~1.0.2"
+ has-cors "1.1.0"
+ indexof "0.0.1"
+ object-component "0.0.3"
+ parseqs "0.0.5"
+ parseuri "0.0.5"
+ socket.io-parser "~3.3.0"
+ to-array "0.1.4"
+
+socket.io-parser@~3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
+ integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
+ dependencies:
+ component-emitter "1.2.1"
+ debug "~3.1.0"
+ isarray "2.0.1"
+
sockjs-client@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
@@ -13353,6 +13503,11 @@ tmpl@1.0.x:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+to-array@0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
+ integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@@ -14357,6 +14512,13 @@ ws@^6.1.2, ws@^6.2.1:
dependencies:
async-limiter "~1.0.0"
+ws@~6.1.0:
+ version "6.1.4"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
+ integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
+ dependencies:
+ async-limiter "~1.0.0"
+
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
@@ -14367,6 +14529,11 @@ xmlchars@^2.1.1:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+xmlhttprequest-ssl@~1.5.4:
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
+ integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
+
xregexp@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"
@@ -14490,3 +14657,8 @@ yargs@^13.3.0:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.1"
+
+yeast@0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
+ integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
diff --git a/env.tar.enc b/env.tar.enc
new file mode 100644
index 00000000..941672d8
Binary files /dev/null and b/env.tar.enc differ