Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat : Editor 코드 복사, 저장(api 작업 필요), 커스텀 테마 구현 #73

Merged
merged 7 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions public/monaco-themes/dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"token": "comment"
},
{
"foreground": "666969",
"foreground": "DAB135",
"token": "keyword.operator.class"
},
{
Expand Down Expand Up @@ -230,7 +230,7 @@
}
],
"colors": {
"editor.foreground": "#4D4D4C",
"editor.foreground": "#3DADC9",
"editor.background": "#2b2b2b",
"editor.selectionBackground": "#505050",
"editor.lineHighlightBackground": "#363636",
Expand Down
119 changes: 108 additions & 11 deletions src/components/Post/Editor/IdeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import styled from '@emotion/styled';
import Editor, { loader } from '@monaco-editor/react';
import Editor, { loader
// ,useMonaco
} from '@monaco-editor/react';

import { BsFiles,BsDownload,
// BsLightbulbFill,
// BsLightbulbOffFill
} from 'react-icons/bs';

// Monaco 타입 가져오기
import * as monaco from 'monaco-editor';


interface IdeEditorProps {
defaultLanguage: string;
Expand All @@ -12,7 +23,7 @@ interface IdeEditorProps {

// custom theme json 파일 구조 정의
interface CustomTheme {
base: 'vs' | 'vs-dark' | 'hc-black'; // Built-in theme 타입
base: 'vs' | 'vs-dark' | 'hc-black';
inherit: boolean;
rules: Array<{
token: string;
Expand All @@ -21,7 +32,7 @@ interface CustomTheme {
fontStyle?: string;
}>;
colors: Record<string, string>;
}
};

const IdeEditor: React.FC<IdeEditorProps> = ({
defaultLanguage,
Expand All @@ -31,34 +42,74 @@ const IdeEditor: React.FC<IdeEditorProps> = ({
theme,
}) => {
const [themeLoaded, setThemeLoaded] = useState(false);
// 코드 복사, 저장 : 명시적으로 monaco.editor.IStandaloneCodeEditor 타입 지정
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);

// 코드 하이라이트를 위한 변수
// const monaco = useMonaco();

useEffect(() => {
useEffect(()=>{
// JSON 테마 파일 로드 및 Monaco Editor 초기화
const loadCustomTheme = async () => {
try {
const response = await fetch('/monaco-themes/dark.json'); // public 폴더 기준
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
throw new Error(`HTTP error! 테마 로드 안됨!! Status: ${response.status}`);
}

// 'response.json()'의 결과를 CustomTheme 타입으로 캐스팅
const customTheme = (await response.json()) as CustomTheme;

const monaco = await loader.init(); // Monaco 로더 초기화
monaco.editor.defineTheme('vs-dark', customTheme); // 커스텀 테마 등록

monaco.editor.defineTheme('custom-dark', customTheme); // 커스텀 테마 등록
setThemeLoaded(true); // 테마 로드 완료
} catch (error) {
console.error('Failed to load Monaco theme:', (error as Error).message);
}
};

loadCustomTheme();
}, []);
},[])

if (!themeLoaded) {
return <div>Loading Editor...</div>;
}

const handleEditorMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
editorRef.current = editor;
};

const handleCopyButton = async () => {
if (editorRef.current) {
const currentCode = editorRef.current.getValue();
try {
await navigator.clipboard.writeText(currentCode);
alert('코드가 클립보드에 복사되었습니다!');
} catch (error) {
console.error('클립보드 복사 실패:', error);
alert('클립보드 복사에 실패했습니다.');
}
} else {
alert('Editor가 초기화되지 않았습니다.');
}
};

// 추후 api연동시 수정
const handleSaveButton = async() =>{
if(editorRef.current){
const currentCode = editorRef.current.getValue();
try{

alert('save!')
console.log('save currentCode : \n',currentCode);
} catch (error){
console.error('코드 저장 실패')
}
}else {
console.log('editor가 초기화 되지 않음.')
}
}

return (
<Container>
<Editor
Expand All @@ -68,22 +119,41 @@ const IdeEditor: React.FC<IdeEditorProps> = ({
defaultValue={defaultValue}
language={language}
value={value}
theme={theme} // 'custom-dark' 또는 기본 테마 이름 전달 가능
theme={theme}
onMount={handleEditorMount}

options={{
minimap:{ enabled:false },
scrollbar:{
alwaysConsumeMouseWheel:true,
vertical : 'auto',
verticalScrollbarSize : 5,
horizontal : 'auto'
}
}}
/>

<ButtonContainer>
<CopyButton onClick={handleCopyButton} />
<SaveButton onClick={handleSaveButton}/>
</ButtonContainer>
</Container>
);
};

export default IdeEditor;


const Container = styled.div`
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;

margin: 0;
padding: 0.62rem 1.19rem;
padding: 0.62rem;
box-sizing: border-box;

width: 47.9375rem;
Expand All @@ -99,3 +169,30 @@ const Container = styled.div`
background: var(--background, #2b2b2b);
box-shadow: 0px 0px 4px 0px var(--black, #161616) inset;
`;

const ButtonContainer=styled.div`
z-index: 5;
display: flex;
flex-direction: column;
justify-content: space-between;

color: var(--gray);
`;

const CopyButton=styled(BsFiles)`
width: 1.5rem;
height: 1.5rem;

position: absolute;
right: 1.5rem;
top: 1.5rem;
`;

const SaveButton=styled(BsDownload)`
width: 1.5rem;
height: 1.5rem;

position: absolute;
right: 1.5rem;
bottom : 1.5rem;
`;
18 changes: 3 additions & 15 deletions src/pages/Post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,18 @@ const Post: React.FC = () => {
// props api와 미일치
boardName : '9oorm_KDT',
defaultValue:'// [FE] 모달창 컴포넌트 만들기2',
change_language:'typescript',
value:'let num:number = 10;',
theme:'vs-dark'
change_language:'javascript',
value:`// [FE] 모달창 컴포넌트 만들기2 code`,
theme:'custom-dark'
}

// 테스트용
// const [value,setValue] = useState<string>(dummyData.defaultValue);
// const handleChangeValue = (newValue : string) : void => {
// setValue(newValue);
// };

return (
<Container>
<Header
boardName={dummyData.boardName}
postName={dummyData.name}
/>
<Body>
{/* 테스트 용 input */}
{/* <input
type='text'
value={value}
onChange={(e)=>handleChangeValue(e.target.value)} // 상태 업데이트
/> */}
<IdeEditor
defaultLanguage={dummyData.language}
defaultValue={dummyData.defaultValue}
Expand Down
Loading