diff --git a/cocode/src/actions/Project.js b/cocode/src/actions/Project.js index 0c84f278..bf32a06f 100644 --- a/cocode/src/actions/Project.js +++ b/cocode/src/actions/Project.js @@ -3,6 +3,7 @@ import { UPDATE_CODE, UPDATE_CODE_FROM_FILE_ID, FETCH_PROJECT, + UPDATE_FILES, SELECT_FILE, UPDATE_FILE_NAME, CREATE_FILE, @@ -21,6 +22,10 @@ function fetchProjectActionCreator(payload) { return { type: FETCH_PROJECT, payload }; } +function updateFilesActionCreator(payload) { + return { type: UPDATE_FILES, payload }; +} + function updateCodeActionCreator(payload) { return { type: UPDATE_CODE, payload }; } @@ -65,6 +70,7 @@ export { updateCodeActionCreator, updateCodeFromFileIdActionCreator, fetchProjectActionCreator, + updateFilesActionCreator, selectFileActionCreator, updateFileNameActionCreator, createFileActionCreator, diff --git a/cocode/src/actions/types.js b/cocode/src/actions/types.js index 6532f671..7f8aace7 100644 --- a/cocode/src/actions/types.js +++ b/cocode/src/actions/types.js @@ -9,6 +9,7 @@ const UPDATE_PROJECT_INFO = 'updateProjectInfo'; const UPDATE_CODE = 'updateCode'; const UPDATE_CODE_FROM_FILE_ID = 'updateCodeFromFileId'; const FETCH_PROJECT = 'fetchProject'; +const UPDATE_FILES = 'updateFiles'; const SELECT_FILE = 'selectFile'; const CREATE_FILE = 'createFile'; const UPDATE_FILE_NAME = 'updateFileName'; @@ -38,6 +39,7 @@ export { UPDATE_CODE, UPDATE_CODE_FROM_FILE_ID, FETCH_PROJECT, + UPDATE_FILES, SELECT_FILE, UPDATE_FILE_NAME, CREATE_FILE, diff --git a/cocode/src/components/Common/GlobalStyle/index.js b/cocode/src/components/Common/GlobalStyle/index.js index c4975b3c..62d81b40 100644 --- a/cocode/src/components/Common/GlobalStyle/index.js +++ b/cocode/src/components/Common/GlobalStyle/index.js @@ -81,6 +81,14 @@ const GlobalStyle = createGlobalStyle` div, span{ user-select: none; } + + .blink{ + animation: blink 1s step-start 0s infinite; + } + + @keyframes blink { + 50% { opacity: 0.0; } + } `; export default GlobalStyle; diff --git a/cocode/src/components/Common/UserProfile/avatar.jpeg b/cocode/src/components/Common/UserProfile/avatar.jpeg deleted file mode 100644 index 3c082f33..00000000 Binary files a/cocode/src/components/Common/UserProfile/avatar.jpeg and /dev/null differ diff --git a/cocode/src/components/Common/UserProfile/down.svg b/cocode/src/components/Common/UserProfile/down.svg new file mode 100644 index 00000000..8471012a --- /dev/null +++ b/cocode/src/components/Common/UserProfile/down.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/cocode/src/components/Common/UserProfile/index.js b/cocode/src/components/Common/UserProfile/index.js index 43b0ac47..47ca89e5 100644 --- a/cocode/src/components/Common/UserProfile/index.js +++ b/cocode/src/components/Common/UserProfile/index.js @@ -1,13 +1,16 @@ import React from 'react'; import * as Styled from './style'; + import DropDownMenu from 'components/Common/DropDownMenu'; +import down from './down.svg'; function UserProfile({ username, avatar, menuItems }) { return ( {username} + - + ); diff --git a/cocode/src/components/Common/UserProfile/style.js b/cocode/src/components/Common/UserProfile/style.js index df4d63ad..e727023b 100644 --- a/cocode/src/components/Common/UserProfile/style.js +++ b/cocode/src/components/Common/UserProfile/style.js @@ -20,8 +20,21 @@ const UserAvatar = styled.img` width: 3rem; height: 3rem; border-radius: 0.5rem; - cursor: pointer; } `; -export { UserProfile, UserName, UserAvatar }; \ No newline at end of file +const DownArrow = styled.img` + & { + width: 1rem; + height: 1rem; + margin: auto 0 auto 1rem; + cursor: pointer; + filter: invert(0.3); + } + + &:hover { + filter: invert(0); + } +`; + +export { UserProfile, UserName, UserAvatar, DownArrow }; \ No newline at end of file diff --git a/cocode/src/components/Project/BrowserV2/index.js b/cocode/src/components/Project/BrowserV2/index.js index e7c8a6b7..8279a39f 100644 --- a/cocode/src/components/Project/BrowserV2/index.js +++ b/cocode/src/components/Project/BrowserV2/index.js @@ -8,6 +8,7 @@ import React, { import { useParams } from 'react-router-dom'; import * as Styled from './style'; +import open from './open.svg'; import search from './search.svg'; import addToast from 'components/Common/Toast'; @@ -30,6 +31,7 @@ import { KEY_CODE_ENTER } from 'constants/keyCode'; const MIN_WAIT_TIME = 1500; const UPDATE_PROJECT = 'updateProject'; const PROTOCOLS = ['http://', 'https://']; +const NEW_COCONUT = `${COCONUT_SERVER}/new`; function BrowserV2({ ...props }) { const { projectId } = useParams(); @@ -137,6 +139,13 @@ function BrowserV2({ ...props }) { iframeReference.current.contentWindow.postMessage(data, '*'); }, [project]); + const handleClickOpenTab = () => { + const coconutUrl = addressReference.current.value; + if (NEW_COCONUT === coconutUrl) + return addToast.error(NOTIFICATION.NEED_TO_SAVE); + window.open(coconutUrl, '_blank'); + }; + useEffect(handleChangeCurrentURL, [projectId]); useEffect(handleUpdateDependency, [dependencyInstalling]); useEffect(handleUpdateFile, [files]); @@ -154,6 +163,12 @@ function BrowserV2({ ...props }) { defaultValue={addressInputURL} onKeyUp={handleAddressInputKeyDown} /> + + + \ 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 4b547453..7f5948c6 100644 --- a/cocode/src/components/Project/BrowserV2/style.js +++ b/cocode/src/components/Project/BrowserV2/style.js @@ -15,31 +15,6 @@ const BrowserV2 = styled.iframe` } `; -const LoadingOverlay = styled.section` - & { - position: fixed; - top: 12vh; - left: 0; - z-index: 100; - - height: 100vh; - width: 100vw; - - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - background-color: ${BROWSER_THEME.loadingOverlayBGColor}; - - p { - margin-top: 2rem; - font-size: 3rem; - font-weight: lighter; - } - } -`; - const AddressContainer = styled.div` & { display: flex; @@ -70,11 +45,20 @@ const SearchIcon = styled.img` } `; +const OpenIcon = styled.img` + & { + height: 100%; + padding: 0.4rem 0; + background: ${BROWSER_THEME.addressInputBGColor}; + cursor: pointer; + } +`; + export { Frame, BrowserV2, - LoadingOverlay, AddressContainer, AddressInput, - SearchIcon + SearchIcon, + OpenIcon }; diff --git a/cocode/src/components/Project/DependencySearchItem/style.js b/cocode/src/components/Project/DependencySearchItem/style.js index 6a9641a8..4215b123 100644 --- a/cocode/src/components/Project/DependencySearchItem/style.js +++ b/cocode/src/components/Project/DependencySearchItem/style.js @@ -16,8 +16,9 @@ const Item = styled.li` const Description = styled.div` & { display: flex; - margin-top: 0.3rem; + justify-content: flex-end; align-items: center; + margin-top: 0.5rem; } `; diff --git a/cocode/src/components/Project/File/index.js b/cocode/src/components/Project/File/index.js index fa2f5f47..8aa20805 100644 --- a/cocode/src/components/Project/File/index.js +++ b/cocode/src/components/Project/File/index.js @@ -101,7 +101,12 @@ function File({ if (isNotChangeableFileName({ files, parentId, changedName })) { currentTarget.textContent = fileName; - addToast.error(NOTIFICATION.FILE_IS_DUPLICATED); + addToast.error(NOTIFICATION.FILE_NAME_IS_INCORRECT); + return; + } + + if (projectId === 'new') { + successHandler[UPDATE_FILE_NAME](); return; } @@ -123,6 +128,11 @@ function File({ const acceptDeleteThisFile = confirm(NOTIFICATION.CONFIRM_DELETE_FILE); if (!acceptDeleteThisFile) return; + if (projectId === 'new') { + successHandler[DELETE_FILE](_id); + return; + } + const deleteFileId = _id; const deleteFileAPI = deleteFileAPICreator(projectId, deleteFileId, { parentId diff --git a/cocode/src/components/Project/NewFile/index.js b/cocode/src/components/Project/NewFile/index.js index bfcb42f2..39f0599c 100644 --- a/cocode/src/components/Project/NewFile/index.js +++ b/cocode/src/components/Project/NewFile/index.js @@ -1,6 +1,7 @@ import * as React from 'react'; import { useState, useEffect, useContext, useRef } from 'react'; import { useParams } from 'react-router-dom'; +import ObjectID from 'bson-objectid'; import * as Styled from './style'; import addToast from 'components/Common/Toast'; @@ -30,17 +31,38 @@ function NewFile({ depth, type, parentId, handleEndCreateFile }) { const fileNameInputReference = useRef(null); const [{ data, error }, setRequest] = useFetch({}); - const isDuplicatedFileName = fileName => { - return files[parentId].child - .map(id => files[id].name) - .some(name => name === fileName); + const isIncorrectFileName = fileName => { + return ( + fileName.trim() === '' || + files[parentId].child + .map(id => files[id].name) + .some(name => name === fileName) + ); + }; + + const updateFileState = newFileId => { + const createFileAction = createFileActionCreator({ + newFileId, + name: fileName, + parentId, + type + }); + dispatchProject(createFileAction); + changeDivEditable(fileNameInputReference.current, false); }; const requestCreateFile = e => { const name = e.currentTarget.textContent; - if (isDuplicatedFileName(name)) { + if (isIncorrectFileName(name)) { e.preventDefault(); - addToast.error(NOTIFICATION.FILE_IS_DUPLICATED); + addToast.error(NOTIFICATION.FILE_NAME_IS_INCORRECT); + return; + } + + if (projectId === 'new') { + const newFileId = ObjectID().str; + updateFileState(newFileId); + return; } @@ -69,14 +91,7 @@ function NewFile({ depth, type, parentId, handleEndCreateFile }) { if (!data) return; const { newFileId } = data; - const createFileAction = createFileActionCreator({ - newFileId, - name: fileName, - parentId, - type - }); - dispatchProject(createFileAction); - changeDivEditable(fileNameInputReference.current, false); + updateFileState(newFileId); }; const handleErrorResponse = () => { diff --git a/cocode/src/components/Project/PlusImage/style.js b/cocode/src/components/Project/PlusImage/style.js index 3dceb5db..f04f19cc 100644 --- a/cocode/src/components/Project/PlusImage/style.js +++ b/cocode/src/components/Project/PlusImage/style.js @@ -5,7 +5,6 @@ const Image = styled.svg` & { width: ${DEPENDENCY_TAB_THEME.dependencyTabIconSize}; height: ${DEPENDENCY_TAB_THEME.dependencyTabIconSize}; - margin-left: auto; margin-right: 0.5rem; fill: ${DEPENDENCY_TAB_THEME.dependencyTabIconColor} } diff --git a/cocode/src/constants/cursorColors.js b/cocode/src/constants/cursorColors.js new file mode 100644 index 00000000..238ee312 --- /dev/null +++ b/cocode/src/constants/cursorColors.js @@ -0,0 +1,15 @@ +const colors = [ + '#CC000080', + '#FF650F80', + '#FFBA0280', + '#458d5380', + '#006b0480', + '#2E588D80', + '#52338D80', + '#74155180', + '#13133C80', + '#220D3880', + '#00000080' +]; + +export { colors }; \ No newline at end of file diff --git a/cocode/src/constants/notificationMessage.js b/cocode/src/constants/notificationMessage.js index f312f88c..8964aefc 100644 --- a/cocode/src/constants/notificationMessage.js +++ b/cocode/src/constants/notificationMessage.js @@ -8,6 +8,7 @@ const FAIL_TO_UPDATE_PROJECT_CARD = 'Fail to request. sorry, try again please'; const FILE_IS_NOT_MOVABLE = 'This file is not movable'; const FILE_IS_NOT_EDITABLE = 'This file is not editable'; const FILE_IS_DUPLICATED = 'This file name is duplicated'; +const FILE_NAME_IS_INCORRECT = 'This file name is incorrect'; const FILE_IS_NOT_REMOVABLE = 'This file is not removable'; const CONFIRM_DELETE_FILE = 'Are you delete this file?'; @@ -21,6 +22,7 @@ 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'; +const NEED_TO_SAVE = 'Please change your code and save'; export { FAIL_INSTALL_DEPENDENCY, @@ -32,6 +34,7 @@ export { FILE_IS_NOT_MOVABLE, FILE_IS_NOT_EDITABLE, FILE_IS_DUPLICATED, + FILE_NAME_IS_INCORRECT, FILE_IS_NOT_REMOVABLE, CONFIRM_DELETE_FILE, CONFIRM_DELETE_COCONUT, @@ -41,4 +44,5 @@ export { SUCCESS_FORK, CONFLICT_FORK, SHUT_DOWN_LIVE_SHARE, + NEED_TO_SAVE }; diff --git a/cocode/src/containers/Live/Editor/index.js b/cocode/src/containers/Live/Editor/index.js index c75558fc..5f092bb2 100644 --- a/cocode/src/containers/Live/Editor/index.js +++ b/cocode/src/containers/Live/Editor/index.js @@ -5,6 +5,7 @@ import * as Styled from './style'; import FileTabBar from 'components/Project/FileTabBar'; import MonacoEditor from 'components/Project/MonacoEditor'; +import { colors } from 'constants/cursorColors'; import { LiveContext, UserContext, ProjectContext } from 'contexts'; import { updateCodeActionCreator, @@ -13,7 +14,7 @@ import { import useFetch from 'hooks/useFetch'; -import { CursorWidget } from 'utils/monacoWidget'; +import { LabelWidget, CaratWidget } from 'utils/monacoWidget'; let timer; const DEBOUNCING_TIME = 1000; @@ -28,11 +29,12 @@ const MAX_RANGE = { const userCursor = {}; -function Editor({ handleForkCoconut }) { +function Editor({ isConnected }) { const { user } = useContext(UserContext); const { projectId } = useParams(); const { project, dispatchProject } = useContext(ProjectContext); - const { socket } = useContext(LiveContext); + const liveContext = useContext(LiveContext); + const { socket } = liveContext; const [code, setCode] = useState(project.editingCode); const [isEditorMounted, setIsEditorMounted] = useState(false); const [_, setRequest] = useFetch({}); @@ -54,7 +56,7 @@ function Editor({ handleForkCoconut }) { }, DEBOUNCING_TIME); }; - const handleChnageSelectedFileMonaco = ( + const handleChangeSelectedFileMonaco = ( source, text, range = MAX_RANGE @@ -80,7 +82,7 @@ function Editor({ handleForkCoconut }) { selectedRef.current = selectedFileId; setCode(project.editingCode); - handleChnageSelectedFileMonaco( + handleChangeSelectedFileMonaco( 'changeFile', filesRef.current[selectedFileId].contents ); @@ -132,18 +134,19 @@ function Editor({ handleForkCoconut }) { if (!isEditorMounted) return; selectedRef.current = selectedFileId; isBusy.current = true; - handleChnageSelectedFileMonaco('initial', project.editingCode); + handleChangeSelectedFileMonaco('initial', project.editingCode); }, [isEditorMounted]); useEffect(() => { //initialize if (!socket) return; if (!isEditorMounted) return; + if (!isConnected) return; isBusy.current = false; filesRef.current = JSON.parse(JSON.stringify(files)); socket.on('change', handleOnChangeCode); socket.on('moveCursor', handleMoveCursor); - }, [socket, isEditorMounted]); + }, [socket, isEditorMounted, isConnected]); const handleOnChangeCode = (socketId, fileId, op) => { if (socket.id === socketId) { @@ -161,10 +164,7 @@ function Editor({ handleForkCoconut }) { const changedCode = `${str1}${op.text}${str2}`; filesRef.current[fileId].contents = changedCode; const updateCodeFromFileIdAction = updateCodeFromFileIdActionCreator( - { - fileId, - changedCode - } + { fileId, changedCode } ); dispatchProject(updateCodeFromFileIdAction); return; @@ -181,7 +181,7 @@ function Editor({ handleForkCoconut }) { .getModel() .getPositionAt(rangeOffset + rangeLength); - handleChnageSelectedFileMonaco(socketId, text, { + handleChangeSelectedFileMonaco(socketId, text, { startLineNumber: startPosition.lineNumber, startColumn: startPosition.column, endLineNumber: endPosition.lineNumber, @@ -190,18 +190,32 @@ function Editor({ handleForkCoconut }) { }; const handleMoveCursor = (username, fileId, position) => { + const min = 0; + const max = colors.length - 1; if (!userCursor[username]) { - const widget = new CursorWidget( + const color = colors[Math.floor(Math.random() * (min, max))]; + const label = new LabelWidget( editorRef.current, username, - position + position, + color ); - userCursor[username] = widget; - editorRef.current.addContentWidget(widget); + const carat = new CaratWidget( + editorRef.current, + position, + color + ); + userCursor[username] = { label, carat }; + editorRef.current.addContentWidget(label); + editorRef.current.addContentWidget(carat); + } + if (selectedRef.current === fileId) { + userCursor[username].label.showCursor(position); + userCursor[username].carat.showCursor(position); + } else { + userCursor[username].label.hiddenCursor(); + userCursor[username].carat.hiddenCursor(); } - if (selectedRef.current === fileId) - userCursor[username].showCursor(position); - else userCursor[username].hiddenCursor(); }; return ( diff --git a/cocode/src/containers/Live/LiveOnTab/index.js b/cocode/src/containers/Live/LiveOnTab/index.js index 61529764..9f140b7b 100644 --- a/cocode/src/containers/Live/LiveOnTab/index.js +++ b/cocode/src/containers/Live/LiveOnTab/index.js @@ -40,7 +40,12 @@ function LiveOnTab() { {ON_DESCRIPTION} - + {url} {user === owner ? ( @@ -56,4 +61,4 @@ function LiveOnTab() { ); } -export default LiveOnTab; \ No newline at end of file +export default LiveOnTab; diff --git a/cocode/src/pages/Live/index.js b/cocode/src/pages/Live/index.js index f5d490da..ab055c51 100644 --- a/cocode/src/pages/Live/index.js +++ b/cocode/src/pages/Live/index.js @@ -20,7 +20,10 @@ import addToast from 'components/Common/Toast'; import { LiveContext, ProjectContext, UserContext } from 'contexts'; import { ProjectReducer } from 'reducers'; -import { fetchProjectActionCreator } from 'actions/Project'; +import { + fetchProjectActionCreator, + updateFilesActionCreator +} from 'actions/Project'; import { liveOnActionCreator, liveOffActionCreator, @@ -32,7 +35,10 @@ 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 { + SHUT_DOWN_LIVE_SHARE, + LOADING_LIVE +} from 'constants/notificationMessage'; import { getCookie } from 'utils/controlCookie'; const DEFAULT_CLICKED_TAB_INDEX = 0; @@ -77,6 +83,12 @@ function Live() { }; const handleAlreadyExistRoom = ({ host, project, participants }) => { + const { files } = project; + const filesCopy = JSON.parse(JSON.stringify(files)); + const updateFilesAction = updateFilesActionCreator({ + files: filesCopy + }); + dispatchProject(updateFilesAction); dispatchLive( liveOnActionCreator({ socket, @@ -150,14 +162,14 @@ function Live() { setClickedTabIndex }} > -
+
{isFetched && ( - + diff --git a/cocode/src/pages/Project/index.js b/cocode/src/pages/Project/index.js index ce1ea936..563eacbe 100644 --- a/cocode/src/pages/Project/index.js +++ b/cocode/src/pages/Project/index.js @@ -29,7 +29,7 @@ import { LOADING_PROJECT } from 'constants/notificationMessage'; -const DEFAULT_CLICKED_TAB_INDEX = 0; +const DEFAULT_CLICKED_TAB_INDEX = 1; function Project() { const { user } = useContext(UserContext); @@ -121,7 +121,7 @@ function Project() { forkCoconut }} > -
+
{isFetched && ( diff --git a/cocode/src/reducers/ProjectReducer.js b/cocode/src/reducers/ProjectReducer.js index dea3c481..a003b9bd 100644 --- a/cocode/src/reducers/ProjectReducer.js +++ b/cocode/src/reducers/ProjectReducer.js @@ -1,6 +1,7 @@ // 참고: https://github.com/dal-lab/frontend-tdd-examples/blob/master/6-todo-redux/src/reducers.js import { UPDATE_PROJECT_INFO, + UPDATE_FILES, UPDATE_CODE, UPDATE_CODE_FROM_FILE_ID, FETCH_PROJECT, @@ -94,6 +95,13 @@ function getDependencyList(files, root) { }, {}); } +const updateFiles = (state, { files }) => { + return { + ...state, + files + }; +}; + // Update code const updateCode = (state, { changedCode }) => { return { @@ -225,11 +233,15 @@ const deleteFile = (state, { deleteFileId }) => { id => id !== deleteFileId ); + const updatedFiles = Object.entries(files).reduce((acc, [fileId, file]) => { + if (fileId === deleteFileId) return acc; + return { ...acc, [fileId]: file }; + }, {}); + return { ...state, files: { - ...state.files, - [deleteFileId]: undefined, + ...updatedFiles, [parentId]: { ...state.files[parentId], child: updatedParentChilds @@ -328,6 +340,7 @@ function ProjectReducer(state, { type, payload }) { const reducers = { [UPDATE_PROJECT_INFO]: updateProjectInfo, [FETCH_PROJECT]: fetchProject, + [UPDATE_FILES]: updateFiles, [UPDATE_CODE]: updateCode, [UPDATE_CODE_FROM_FILE_ID]: updateCodeFromFileId, [SELECT_FILE]: selectFile, diff --git a/cocode/src/template/react.js b/cocode/src/template/react.js index 3ffca09f..94b18437 100644 --- a/cocode/src/template/react.js +++ b/cocode/src/template/react.js @@ -1,71 +1,5 @@ const template = { - css: - '.App {\n' + - ' font-family: sans-serif;\n' + - ' text-align: center;\n' + - '}\n', - html: - '\n' + - '\n' + - '\n' + - '\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\t\n' + - '\tReact App\n' + - '\n' + - '\n' + - '\n' + - '\t\n' + - '\t
\n' + - '\t\n' + - '\n' + - '\n' + - '', - js: - 'import React from "react";\n' + - 'import ReactDOM from "react-dom";\n' + - '\n' + - 'import "./styles.css";\n' + - '\n' + - 'function App() {\n' + - ' return (\n' + - '
\n' + - '

Hello CodeSandbox

\n' + - '

Start editing to see some magic happen!

\n' + - '
\n' + - ' );\n' + - '}\n' + - '\n' + - 'const rootElement = document.getElementById("root");\n' + - 'ReactDOM.render(, rootElement);\n', - package: + 'package.json': '{\n' + ' "name": "new",\n' + ' "version": "1.0.0",\n' + @@ -75,58 +9,31 @@ const template = { ' "dependencies": {\n' + ' "react": "16.8.6",\n' + ' "react-dom": "16.8.6",\n' + - ' "react-scripts": "3.0.1"\n' + - ' },\n' + - ' "devDependencies": {\n' + - ' "typescript": "3.3.3"\n' + - ' },\n' + - ' "scripts": {\n' + - ' "start": "react-scripts start",\n' + - ' "build": "react-scripts build",\n' + - ' "test": "react-scripts test --env=jsdom",\n' + - ' "eject": "react-scripts eject"\n' + - ' },\n' + - ' "browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"]\n' + + ' "styled-components": "4.4.1"\n' + + ' }\n' + '}\n', - version1: - 'import React, { useState } from "react";\n' + + 'index.js': + 'import React from "react";\n' + 'import ReactDOM from "react-dom";\n' + + 'import * as Styled from "./style"\n' + '\n' + 'function App() {\n' + - ' const [state, setState] = useState("Cocode");\n' + - '\n' + - ' return(\n' + - ' <>\n' + - '

Hi! {state}

\n' + - ' \n' + + ' return (\n' + + ' \n' + + '

🥥 Hello Cocode World 🥥

\n' + + '
\n' + ' )\n' + '}\n' + '\n' + 'ReactDOM.render(, document.getElementById("coconut-root"));\n', - Apple: - 'import React from "react";\n' + - '\n' + - 'function Apple() {\n' + - ' return(\n' + - ' <>\n' + - '

Apple!

\n' + - ' \n' + - ' )\n' + - '}\n' + + 'style.js': + 'import styled from "styled-components";\n' + '\n' + - 'export default Apple;\n', - Banana: - 'import React from "react";\n' + - '\n' + - 'function Banana() {\n' + - ' return(\n' + - ' <>\n' + - '

Banana!

\n' + - ' \n' + - ' )\n' + - '}\n' + + 'const App = styled.div`\n' + + ' text-align: center;\n' + + '`\n' + '\n' + - 'export default Banana;\n' + 'export { App };\n' }; function getTemplate(file) { @@ -153,46 +60,28 @@ const reactTemplate = () => ({ projectId: '5dd61901353f4858e1b5a9d0', name: 'src', type: 'directory', - child: [ - '5dd553be4561ae2bae9cb45d', - '5dd553be4561ae2bae9cb45e', - '5dd553be4561ae2bae9cb461' - ] + child: ['5dd553be4561ae2bae9cb45d', '5dd553be4561ae2bae9cb45e'] }, { _id: '5dd553be4561ae2bae9cb45d', name: 'index.js', projectId: '5dd61901353f4858e1b5a9d0', type: 'js', - contents: getTemplate('version1') + contents: getTemplate('index.js') }, { _id: '5dd553be4561ae2bae9cb45e', - name: 'Apple.js', - projectId: '5dd61901353f4858e1b5a9d0', - type: 'js', - contents: getTemplate('Apple') - }, - { - _id: '5dd553be4561ae2bae9cb461', - projectId: '5dd61901353f4858e1b5a9d0', - name: 'Component', - type: 'directory', - child: ['5dd553be4561ae2bae9cb460'] - }, - { - _id: '5dd553be4561ae2bae9cb460', - name: 'Banana.js', + name: 'style.js', projectId: '5dd61901353f4858e1b5a9d0', type: 'js', - contents: getTemplate('Banana') + contents: getTemplate('style.js') }, { _id: '5dd553be4561ae2bae9cb462', name: 'package.json', projectId: '5dd61901353f4858e1b5a9d0', type: 'npm', - contents: getTemplate('package') + contents: getTemplate('package.json') } ] }); diff --git a/cocode/src/utils/monacoWidget.js b/cocode/src/utils/monacoWidget.js index 83bbe24a..8900e241 100644 --- a/cocode/src/utils/monacoWidget.js +++ b/cocode/src/utils/monacoWidget.js @@ -1,9 +1,10 @@ -class CursorWidget { - constructor(editor, userName, position) { +class LabelWidget { + constructor(editor, userName, position, color) { this.editor = editor; this.id = userName; this.domNode = null; this.position = position; + this.color = color; } getId() { @@ -14,7 +15,10 @@ class CursorWidget { if (!this.domNode) { this.domNode = document.createElement('div'); this.domNode.innerHTML = this.id; - this.domNode.style.background = 'grey'; + this.domNode.style.fontSize = '0.5rem'; + this.domNode.style.background = this.color; + this.domNode.style.color = 'white'; + this.domNode.style.transform = 'translateX(3px)'; this.domNode.id = this.id; } return this.domNode; @@ -43,4 +47,50 @@ class CursorWidget { } } -export { CursorWidget }; +class CaratWidget { + constructor(editor, position, color) { + this.editor = editor; + this.domNode = null; + this.position = position; + this.color = color; + } + + getId() { + return this.id; + } + + getDomNode() { + if (!this.domNode) { + this.domNode = document.createElement('div'); + this.domNode.style.background = this.color; + this.domNode.classList.add('blink'); + this.domNode.style.width = '0.2rem'; + this.domNode.style.height = '1.5rem'; + } + 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 { LabelWidget, CaratWidget }; \ No newline at end of file