Skip to content

Commit 58a54ea

Browse files
committed
feat: bisc 이벤트 대응
1 parent 8cc98f9 commit 58a54ea

File tree

5 files changed

+123
-0
lines changed

5 files changed

+123
-0
lines changed

client/postcss.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}

client/src/App.css

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
@tailwind components;
2+
@tailwind utilities;
3+
14
.no-drag {
25
-ms-user-select: none;
36
-moz-user-select: -moz-none;

client/src/App.js

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useState, useEffect } from "react";
99
import { Provider } from "react-redux";
1010
import store from "./redux/store";
1111
import Draggable from "react-draggable";
12+
import ConditionalBanner from "./components/ConditionalBanner";
1213

1314
function App() {
1415
const [section, setSection] = useState("1");
@@ -87,6 +88,7 @@ function App() {
8788
alignItems: "center",
8889
}}
8990
></div>
91+
<ConditionalBanner />
9092
<div className="scale-wrapper" style={{ transform: `scale(${scale})` }}>
9193
<Draggable onDrag={(e, data) => trackPos(data)}>
9294
<div className="form no-drag">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
3+
const MovingBanner = () => {
4+
const [isDragging, setIsDragging] = useState(false);
5+
const [startX, setStartX] = useState(0);
6+
const [scrollLeft, setScrollLeft] = useState(0);
7+
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true);
8+
const containerRef = useRef(null);
9+
10+
const texts = ["제 10회 BoB 오픈 정보보안 컨퍼런스", "BISC 2024"];
11+
const repeatedTexts = [...Array(12)].flatMap(() => texts);
12+
13+
useEffect(() => {
14+
let animationFrame;
15+
let speed = 1;
16+
17+
const animate = () => {
18+
if (!isDragging && autoScrollEnabled && containerRef.current) {
19+
setScrollLeft(prev => {
20+
const newPosition = prev + speed;
21+
if (newPosition > containerRef.current.scrollWidth / 2) {
22+
return 0;
23+
}
24+
return newPosition;
25+
});
26+
}
27+
animationFrame = requestAnimationFrame(animate);
28+
};
29+
30+
animationFrame = requestAnimationFrame(animate);
31+
32+
return () => {
33+
cancelAnimationFrame(animationFrame);
34+
};
35+
}, [isDragging, autoScrollEnabled]);
36+
37+
const handleMouseDown = (e) => {
38+
setIsDragging(true);
39+
setAutoScrollEnabled(false);
40+
setStartX(e.pageX - scrollLeft);
41+
};
42+
43+
const handleMouseUp = () => {
44+
setIsDragging(false);
45+
setTimeout(() => setAutoScrollEnabled(true), 1000);
46+
};
47+
48+
const handleMouseMove = (e) => {
49+
if (!isDragging) return;
50+
const x = e.pageX - startX;
51+
setScrollLeft(x);
52+
};
53+
54+
return (
55+
<div
56+
className="w-full overflow-hidden fixed top-0 left-0 z-50"
57+
style={{
58+
backgroundColor: 'rgb(87, 81, 254)',
59+
height: '30px' // 배너 높이 고정
60+
}}
61+
>
62+
<div
63+
ref={containerRef}
64+
className="flex items-center h-full relative cursor-grab active:cursor-grabbing select-none"
65+
onMouseDown={handleMouseDown}
66+
onMouseUp={handleMouseUp}
67+
onMouseLeave={handleMouseUp}
68+
onMouseMove={handleMouseMove}
69+
style={{
70+
transform: `translateX(-${scrollLeft}px)`,
71+
}}
72+
>
73+
{repeatedTexts.map((text, index) => (
74+
<p
75+
key={index}
76+
className="text-white text-sm font-bold whitespace-nowrap mx-4"
77+
>
78+
{text}
79+
</p>
80+
))}
81+
</div>
82+
</div>
83+
);
84+
};
85+
86+
const ConditionalBanner = () => {
87+
const [showBanner, setShowBanner] = useState(false);
88+
89+
useEffect(() => {
90+
const queryParams = new URLSearchParams(window.location.search);
91+
const eventParam = queryParams.get('event');
92+
setShowBanner(eventParam?.toLowerCase() === 'bisc');
93+
}, []);
94+
95+
return showBanner ? <MovingBanner /> : null;
96+
};
97+
98+
export default ConditionalBanner;

client/src/components/Editor.jsx

+14
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export default function Editor({
1818
const [keyEvent, setKeyEvent] = useState("");
1919
const [autoEnter, setAutoEnter] = useState(false);
2020
const [textSplit, setTextSplit] = useState([]);
21+
const [isEventMode, setIsEventMode] = useState(false);
22+
23+
// 이벤트 모드 체크를 위한 useEffect 추가
24+
useEffect(() => {
25+
const queryParams = new URLSearchParams(window.location.search);
26+
const eventParam = queryParams.get('event');
27+
setIsEventMode(eventParam?.toLowerCase() === 'bisc');
28+
}, []);
29+
2130

2231
useEffect(() => {
2332
setTextSplit(getFilecontents(file).content);
@@ -45,6 +54,11 @@ export default function Editor({
4554
}, [userInput]);
4655

4756
const userInputTabHandler = (event) => {
57+
if (isEventMode && (event.key === 'Backspace' || event.key === 'Delete')) {
58+
event.preventDefault();
59+
return;
60+
}
61+
4862
// todo: 조건식 최적화
4963
if (event.key === "Escape") {
5064
setUseAutoComplete(false);

0 commit comments

Comments
 (0)