From 6286790a1af27f39c64745cdfe164f2dd5545b82 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Sun, 24 Nov 2024 13:22:02 +0900 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20content=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=EC=9E=90=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20manifest=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/content.tsx | 3 --- projects/web-filter/src/manifest-content.json | 2 +- projects/web-filter/src/manifest-native.json | 11 ++++++++++- projects/web-filter/src/manifest.json | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 projects/web-filter/src/content.tsx diff --git a/projects/web-filter/src/content.tsx b/projects/web-filter/src/content.tsx deleted file mode 100644 index b1b6ea9..0000000 --- a/projects/web-filter/src/content.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { popup } from './functions/popup'; - -popup.open(); diff --git a/projects/web-filter/src/manifest-content.json b/projects/web-filter/src/manifest-content.json index ba2c6ab..6aeb6d5 100644 --- a/projects/web-filter/src/manifest-content.json +++ b/projects/web-filter/src/manifest-content.json @@ -15,7 +15,7 @@ "content_scripts": [ { "matches": [""], - "js": ["src/content.tsx"] + "js": ["src/content.ts"] } ] } diff --git a/projects/web-filter/src/manifest-native.json b/projects/web-filter/src/manifest-native.json index 40e518a..e9e8dee 100644 --- a/projects/web-filter/src/manifest-native.json +++ b/projects/web-filter/src/manifest-native.json @@ -17,5 +17,14 @@ "background": { "{{chrome}}.service_worker": "src/background.ts", "{{firefox}}.scripts": ["src/background.ts"] - } + }, + "content_scripts": [ + { + "matches": [""], + "js": ["src/content.ts"], + "all_frames": true + } + ], + "permissions": ["activeTab", "tabs"], + "host_permissions": [""] } diff --git a/projects/web-filter/src/manifest.json b/projects/web-filter/src/manifest.json index c18721b..216653b 100644 --- a/projects/web-filter/src/manifest.json +++ b/projects/web-filter/src/manifest.json @@ -21,7 +21,7 @@ "content_scripts": [ { "matches": [""], - "js": ["src/content.tsx"] + "js": ["src/content.ts"] } ] } From 27972df3ff532cc9e1c5bbdb9de7eb37e055647b Mon Sep 17 00:00:00 2001 From: lerrybe Date: Sun, 24 Nov 2024 13:22:30 +0900 Subject: [PATCH 02/14] =?UTF-8?q?chore:=20chrome=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/package.json | 1 + yarn.lock | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/projects/web-filter/package.json b/projects/web-filter/package.json index 3f32d50..a0672e3 100644 --- a/projects/web-filter/package.json +++ b/projects/web-filter/package.json @@ -13,6 +13,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@types/chrome": "^0.0.283", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@types/webextension-polyfill": "^0.10.0", diff --git a/yarn.lock b/yarn.lock index e34b355..8ab738a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2294,6 +2294,16 @@ __metadata: languageName: node linkType: hard +"@types/chrome@npm:^0.0.283": + version: 0.0.283 + resolution: "@types/chrome@npm:0.0.283" + dependencies: + "@types/filesystem": "npm:*" + "@types/har-format": "npm:*" + checksum: 49bb67c0b78517319de00c0c3c0e0ce06b9acad56064044953e6b2a31eff8878e4fd23605865c3986eeba82723724375be7abf5976b256dc08373e390ed51341 + languageName: node + linkType: hard + "@types/css-font-loading-module@npm:^0.0.12": version: 0.0.12 resolution: "@types/css-font-loading-module@npm:0.0.12" @@ -2322,6 +2332,29 @@ __metadata: languageName: node linkType: hard +"@types/filesystem@npm:*": + version: 0.0.36 + resolution: "@types/filesystem@npm:0.0.36" + dependencies: + "@types/filewriter": "npm:*" + checksum: ec831040fe3aff066ffb7b7541e21a5dd59aa06e7175c61e592736e38b018b1d513551438254631e2a3fbc81ff671bf618401000f4c8ea79156934cbc7dcaeaa + languageName: node + linkType: hard + +"@types/filewriter@npm:*": + version: 0.0.33 + resolution: "@types/filewriter@npm:0.0.33" + checksum: 495a4bb424c27eda967fe9ac3b8f7b781e6b3f9ce59403a991590cb1073022f9c5383d3c7d808ef6956b785550c36664c4fcd502dc0baf69e340bd481171e0ca + languageName: node + linkType: hard + +"@types/har-format@npm:*": + version: 1.2.16 + resolution: "@types/har-format@npm:1.2.16" + checksum: b7ecef1ca27b902f9eb0bff9cebe650370f594e20813a728853673b22400afa08966eb5fd725553c19811bc166947e1c845e92ce4df86cee79d4fd9bda4d251b + languageName: node + linkType: hard + "@types/http-cache-semantics@npm:^4.0.2": version: 4.0.4 resolution: "@types/http-cache-semantics@npm:4.0.4" @@ -8675,6 +8708,7 @@ __metadata: version: 0.0.0-use.local resolution: "web-filter@workspace:projects/web-filter" dependencies: + "@types/chrome": "npm:^0.0.283" "@types/react": "npm:^18.0.26" "@types/react-dom": "npm:^18.0.9" "@types/webextension-polyfill": "npm:^0.10.0" From f56e52ab09e6743908f51f5503c28d87aa6f7fbe Mon Sep 17 00:00:00 2001 From: lerrybe Date: Mon, 25 Nov 2024 23:47:53 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20status=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/background.ts | 11 +++++++++++ .../web-filter/src/components/Button/Button.tsx | 7 +++++-- projects/web-filter/src/content.ts | 17 +++++++++++++++++ .../src/pages/PopupNative/PopupNative.tsx | 14 +++++++++++++- projects/web-filter/src/types/status.ts | 5 +++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 projects/web-filter/src/content.ts create mode 100644 projects/web-filter/src/types/status.ts diff --git a/projects/web-filter/src/background.ts b/projects/web-filter/src/background.ts index f25472a..55bdc02 100644 --- a/projects/web-filter/src/background.ts +++ b/projects/web-filter/src/background.ts @@ -5,3 +5,14 @@ console.log('Hello from the background!'); browser.runtime.onInstalled.addListener((details) => { console.log('Extension installed:', details); }); + +chrome.runtime.onMessage.addListener((message, _, sendResponse) => { + try { + if (message.tabId && message.action) { + chrome.tabs.sendMessage(message.tabId, { action: message.action }); + } + sendResponse({ success: true }); + } catch (error) { + sendResponse({ success: false, error }); + } +}); diff --git a/projects/web-filter/src/components/Button/Button.tsx b/projects/web-filter/src/components/Button/Button.tsx index 3e7eeca..930d843 100644 --- a/projects/web-filter/src/components/Button/Button.tsx +++ b/projects/web-filter/src/components/Button/Button.tsx @@ -4,10 +4,13 @@ import styles from './Button.module.css'; type ButtonProps = PropsWithChildren<{ theme?: 'blue' | 'gray'; + onClick?: () => void; }>; -export const Button = ({ children, theme = 'blue' }: ButtonProps) => { +export const Button = ({ children, theme = 'blue', onClick }: ButtonProps) => { return ( - + ); }; diff --git a/projects/web-filter/src/content.ts b/projects/web-filter/src/content.ts new file mode 100644 index 0000000..a804d90 --- /dev/null +++ b/projects/web-filter/src/content.ts @@ -0,0 +1,17 @@ +import { STATUS } from './types/status'; + +chrome.runtime.onMessage.addListener(async (request) => { + switch (request.action) { + case STATUS.INACTIVE: + console.log('INACTIVE'); + break; + case STATUS.SURFING: + console.log('SURFING'); + break; + case STATUS.SELECTED: + console.log('SELECTED'); + break; + default: + break; + } +}); diff --git a/projects/web-filter/src/pages/PopupNative/PopupNative.tsx b/projects/web-filter/src/pages/PopupNative/PopupNative.tsx index 27b4bfa..32a3d9d 100644 --- a/projects/web-filter/src/pages/PopupNative/PopupNative.tsx +++ b/projects/web-filter/src/pages/PopupNative/PopupNative.tsx @@ -1,8 +1,18 @@ import { Button } from '../../components/Button/Button'; import { Header } from '../../components/Header/Header'; +import { STATUS } from '../../types/status'; import styles from './PopupNative.module.css'; export const PopupNative = () => { + const handleStartSelectElement = async () => { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tabId = tabs[0]?.id; + + if (tabId) { + await chrome.runtime.sendMessage({ tabId, action: STATUS.SURFING }); + } + }; + return (
@@ -22,7 +32,9 @@ export const PopupNative = () => {
- +
); diff --git a/projects/web-filter/src/types/status.ts b/projects/web-filter/src/types/status.ts new file mode 100644 index 0000000..d177357 --- /dev/null +++ b/projects/web-filter/src/types/status.ts @@ -0,0 +1,5 @@ +export enum STATUS { + INACTIVE = 'INACTIVE', // 선택기가 비활성화된 상태 + SURFING = 'SURFING', // 마우스가 움직이면서 요소를 탐색 중인 상태 + SELECTED = 'SELECTED', // 요소가 선택된 상태 +} From 58bd3783c8879fb90436b7020d152dcc0adcd57d Mon Sep 17 00:00:00 2001 From: lerrybe Date: Tue, 26 Nov 2024 10:41:47 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20element=20selector=20=EB=BC=88?= =?UTF-8?q?=EB=8C=80=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20bind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/content.ts | 3 +++ .../src/scripts-lib/element-selector.ts | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 projects/web-filter/src/scripts-lib/element-selector.ts diff --git a/projects/web-filter/src/content.ts b/projects/web-filter/src/content.ts index a804d90..9ae40d2 100644 --- a/projects/web-filter/src/content.ts +++ b/projects/web-filter/src/content.ts @@ -1,6 +1,9 @@ +import { ElementSelector } from './scripts-lib/element-selector'; import { STATUS } from './types/status'; chrome.runtime.onMessage.addListener(async (request) => { + const selector = new ElementSelector(); + switch (request.action) { case STATUS.INACTIVE: console.log('INACTIVE'); diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts new file mode 100644 index 0000000..1b8efa7 --- /dev/null +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -0,0 +1,25 @@ +export class ElementSelector { + private selectedElememt: HTMLElement | null = null; + + constructor() { + this.bindEvents(); + } + + private handleMouseMove = (e: MouseEvent) => { + const element = document.elementFromPoint(e.clientX, e.clientY); + console.log(element); + }; + + private handleClick = (e: MouseEvent) => {}; + + private handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + } + }; + + private bindEvents = () => { + window.addEventListener('mousemove', this.handleMouseMove); + window.addEventListener('click', this.handleClick); + window.addEventListener('keydown', this.handleKeyDown); + }; +} From 2b072ecac729cd5b322a0cb3b0e76f94108a612f Mon Sep 17 00:00:00 2001 From: lerrybe Date: Tue, 26 Nov 2024 17:33:36 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20element=20selector=EC=97=90?= =?UTF-8?q?=EC=84=9C=20elementFromPoint=EB=A1=9C=20dom=20=EC=9A=94?= =?UTF-8?q?=EC=86=8C=20=EA=B0=90=EC=A7=80=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/content.ts | 8 +- .../scripts-lib/element-selector.styles.ts | 134 +++++++++++++++ .../src/scripts-lib/element-selector.ts | 158 +++++++++++++++++- 3 files changed, 291 insertions(+), 9 deletions(-) create mode 100644 projects/web-filter/src/scripts-lib/element-selector.styles.ts diff --git a/projects/web-filter/src/content.ts b/projects/web-filter/src/content.ts index 9ae40d2..bd09df5 100644 --- a/projects/web-filter/src/content.ts +++ b/projects/web-filter/src/content.ts @@ -1,18 +1,16 @@ import { ElementSelector } from './scripts-lib/element-selector'; import { STATUS } from './types/status'; -chrome.runtime.onMessage.addListener(async (request) => { - const selector = new ElementSelector(); +const selector = new ElementSelector(); +chrome.runtime.onMessage.addListener(async (request) => { switch (request.action) { case STATUS.INACTIVE: - console.log('INACTIVE'); break; case STATUS.SURFING: - console.log('SURFING'); + selector.surfingElements(); break; case STATUS.SELECTED: - console.log('SELECTED'); break; default: break; diff --git a/projects/web-filter/src/scripts-lib/element-selector.styles.ts b/projects/web-filter/src/scripts-lib/element-selector.styles.ts new file mode 100644 index 0000000..3b28892 --- /dev/null +++ b/projects/web-filter/src/scripts-lib/element-selector.styles.ts @@ -0,0 +1,134 @@ +export const overlayStyles = { + base: ` + position: fixed; + box-sizing: border-box; + transition: all 0.2s ease-in-out; + border-radius: 4px; + z-index: 10000; + pointer-events: none; + `, + + themes: { + default: { + style: ` + background: linear-gradient(45deg, + rgba(255, 255, 255, 0.1), + rgba(255, 255, 255, 0.2) + ); + border: 3px solid transparent; + box-shadow: + 0 0 0 2px rgba(62, 184, 255, 0.3), + 0 0 10px rgba(62, 184, 255, 0.2), + inset 0 0 20px rgba(62, 184, 255, 0.1); + `, + keyframes: ` + @keyframes pulse { + 0% { + box-shadow: + 0 0 0 2px rgba(62, 184, 255, 0.3), + 0 0 10px rgba(62, 184, 255, 0.2), + inset 0 0 20px rgba(62, 184, 255, 0.1); + } + 50% { + box-shadow: + 0 0 0 4px rgba(62, 184, 255, 0.3), + 0 0 15px rgba(62, 184, 255, 0.2), + inset 0 0 30px rgba(62, 184, 255, 0.1); + } + 100% { + box-shadow: + 0 0 0 2px rgba(62, 184, 255, 0.3), + 0 0 10px rgba(62, 184, 255, 0.2), + inset 0 0 20px rgba(62, 184, 255, 0.1); + } + } + `, + }, + + pastel: { + style: ` + background: linear-gradient(45deg, + rgba(255, 182, 193, 0.2), + rgba(255, 218, 185, 0.2), + rgba(255, 255, 224, 0.2), + rgba(176, 224, 230, 0.2) + ); + border: 2px solid rgba(255, 182, 193, 0.3); + box-shadow: + 0 0 10px rgba(255, 182, 193, 0.2), + inset 0 0 20px rgba(176, 224, 230, 0.2); + `, + keyframes: ` + @keyframes pulse { + 0% { + box-shadow: + 0 0 10px rgba(255, 182, 193, 0.2), + inset 0 0 20px rgba(176, 224, 230, 0.2); + } + 50% { + box-shadow: + 0 0 15px rgba(255, 218, 185, 0.3), + inset 0 0 25px rgba(176, 224, 230, 0.3); + } + 100% { + box-shadow: + 0 0 10px rgba(255, 182, 193, 0.2), + inset 0 0 20px rgba(176, 224, 230, 0.2); + } + } + `, + }, + + neon: { + style: ` + background: rgba(0, 0, 0, 0.1); + border: 2px solid #00ff00; + box-shadow: + 0 0 10px #00ff00, + inset 0 0 20px rgba(0, 255, 0, 0.5); + `, + keyframes: ` + @keyframes pulse { + 0% { + box-shadow: + 0 0 10px #00ff00, + 0 0 20px #00ff00, + 0 0 30px #00ff00, + inset 0 0 20px rgba(0, 255, 0, 0.5); + } + 50% { + box-shadow: + 0 0 15px #00ff00, + 0 0 25px #00ff00, + 0 0 35px #00ff00, + inset 0 0 25px rgba(0, 255, 0, 0.7); + } + 100% { + box-shadow: + 0 0 10px #00ff00, + 0 0 20px #00ff00, + 0 0 30px #00ff00, + inset 0 0 20px rgba(0, 255, 0, 0.5); + } + } + `, + }, + + minimal: { + style: ` + background: rgba(0, 0, 0, 0.05); + border: 2px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + `, + keyframes: ` + @keyframes pulse { + 0% { opacity: 0.7; } + 50% { opacity: 1; } + 100% { opacity: 0.7; } + } + `, + }, + }, +} as const; + +export type OverlayTheme = keyof typeof overlayStyles.themes; diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 1b8efa7..9eda282 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -1,25 +1,175 @@ +import { STATUS } from '../types/status'; +import { overlayStyles } from './element-selector.styles'; + export class ElementSelector { - private selectedElememt: HTMLElement | null = null; + private status: STATUS = STATUS.INACTIVE; + private selectedElement: Element | null = null; + private styleSheet: HTMLStyleElement | null = null; + private currentTheme: keyof typeof overlayStyles.themes = 'default'; + private overlay: HTMLElement | null = null; constructor() { this.bindEvents(); } + private setStatus(status: STATUS) { + this.status = status; + this.notifyPopup(); + } + + private notifyPopup() { + chrome.runtime.sendMessage({ + status: this.status, + selectedElement: this.selectedElement ?? null, + }); + } + private handleMouseMove = (e: MouseEvent) => { + if (this.status !== STATUS.SURFING) return; + const element = document.elementFromPoint(e.clientX, e.clientY); - console.log(element); + if (!element) return; + + const overlay = + document.getElementById('web_filter_overlay_element') || + this.createOverlay(); + + const rect = element.getBoundingClientRect(); + overlay.style.left = `${rect.left}px`; + overlay.style.top = `${rect.top}px`; + overlay.style.width = `${rect.width}px`; + overlay.style.height = `${rect.height}px`; + + this.highlightElement(overlay); }; - private handleClick = (e: MouseEvent) => {}; + private createOverlay = () => { + // 기존 오버레이 제거 + if (this.overlay && document.body.contains(this.overlay)) { + document.body.removeChild(this.overlay); + } + + const overlay = document.createElement('div'); + overlay.id = 'web_filter_overlay_element'; + + this.updateOverlayStyle(overlay); + + document.body.appendChild(overlay); + this.overlay = overlay; + return overlay; + }; + + private updateOverlayStyle(overlay: HTMLElement) { + // 기존 스타일시트 제거 + if (this.styleSheet && document.head.contains(this.styleSheet)) { + document.head.removeChild(this.styleSheet); + } + + const theme = overlayStyles.themes[this.currentTheme]; + + // 새 스타일시트 생성 및 적용 + this.styleSheet = document.createElement('style'); + this.styleSheet.textContent = theme.keyframes; + document.head.appendChild(this.styleSheet); + + // 오버레이에 스타일 적용 + overlay.style.cssText = overlayStyles.base + theme.style; + overlay.style.animation = 'pulse 2s infinite'; + } + + private highlightElement = (overlay: HTMLElement) => { + overlay.style.transform = 'scale(1.03)'; + setTimeout(() => (overlay.style.transform = 'scale(1)'), 200); + }; + + private handleClick = (e: MouseEvent) => { + if (this.status !== STATUS.SURFING) return; + + const element = document.elementFromPoint(e.clientX, e.clientY); + if (!element) return; + + this.selectedElement = element; + this.setStatus(STATUS.SELECTED); + + // 선택 효과 적용 + const overlay = document.getElementById('web_filter_overlay_element'); + if (overlay) { + const theme = overlayStyles.themes[this.currentTheme]; + overlay.style.animation = 'none'; + overlay.style.transform = 'scale(1)'; + overlay.style.border = '2px solid ' + this.getThemeColor(); + } + }; + + private getThemeColor(): string { + // 테마별 주요 색상 반환 + switch (this.currentTheme) { + case 'pastel': + return 'rgba(255, 182, 193, 0.5)'; + case 'neon': + return '#00ff00'; + case 'minimal': + return 'rgba(0, 0, 0, 0.2)'; + default: + return 'rgba(62, 184, 255, 0.5)'; + } + } private handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { + this.cleanupOverlay(); + this.setStatus(STATUS.INACTIVE); + this.selectedElement = null; } }; private bindEvents = () => { - window.addEventListener('mousemove', this.handleMouseMove); window.addEventListener('click', this.handleClick); window.addEventListener('keydown', this.handleKeyDown); + window.addEventListener('mousemove', this.handleMouseMove); + }; + + /** + * @description + * + * public methods + */ + public surfingElements = () => { + this.setStatus(STATUS.SURFING); + }; + + public setTheme = (theme: keyof typeof overlayStyles.themes) => { + this.currentTheme = theme; + const overlay = document.getElementById('web_filter_overlay_element'); + + if (overlay) { + this.updateOverlayStyle(overlay); + } + }; + + public cleanupOverlay = () => { + // 스타일시트 제거 + if (this.styleSheet && document.head.contains(this.styleSheet)) { + document.head.removeChild(this.styleSheet); + this.styleSheet = null; + } + + // 오버레이 제거 + if (this.overlay && document.body.contains(this.overlay)) { + document.body.removeChild(this.overlay); + this.overlay = null; + } + }; + + public destroy = () => { + this.cleanupOverlay(); + + window.removeEventListener('click', this.handleClick); + window.removeEventListener('keydown', this.handleKeyDown); + window.removeEventListener('mousemove', this.handleMouseMove); }; + + // TODO: mouseleave? 일 때 overlay 제거해주기 + // 다른 요소가 호버링되면, 감지되면 기존 Overlay 제거? + // 선택한 정보 넘기기 } From fb1786b7054e2fe576a6927f243141b1cc40f1b9 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Tue, 26 Nov 2024 19:38:07 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20overlay=20=20=EC=BB=AC=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts-lib/element-selector.styles.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/projects/web-filter/src/scripts-lib/element-selector.styles.ts b/projects/web-filter/src/scripts-lib/element-selector.styles.ts index 3b28892..6171609 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.styles.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.styles.ts @@ -17,29 +17,29 @@ export const overlayStyles = { ); border: 3px solid transparent; box-shadow: - 0 0 0 2px rgba(62, 184, 255, 0.3), - 0 0 10px rgba(62, 184, 255, 0.2), - inset 0 0 20px rgba(62, 184, 255, 0.1); + 0 0 0 2px rgba(62, 184, 255, 0.4), + 0 0 10px rgba(62, 184, 255, 0.3), + inset 0 0 20px rgba(62, 184, 255, 0.2); `, keyframes: ` @keyframes pulse { 0% { box-shadow: - 0 0 0 2px rgba(62, 184, 255, 0.3), - 0 0 10px rgba(62, 184, 255, 0.2), - inset 0 0 20px rgba(62, 184, 255, 0.1); + 0 0 0 2px rgba(62, 184, 255, 0.4), + 0 0 10px rgba(62, 184, 255, 0.3), + inset 0 0 20px rgba(62, 184, 255, 0.2); } 50% { box-shadow: - 0 0 0 4px rgba(62, 184, 255, 0.3), - 0 0 15px rgba(62, 184, 255, 0.2), - inset 0 0 30px rgba(62, 184, 255, 0.1); + 0 0 0 4px rgba(62, 184, 255, 0.4), + 0 0 15px rgba(62, 184, 255, 0.3), + inset 0 0 30px rgba(62, 184, 255, 0.2); } 100% { box-shadow: - 0 0 0 2px rgba(62, 184, 255, 0.3), - 0 0 10px rgba(62, 184, 255, 0.2), - inset 0 0 20px rgba(62, 184, 255, 0.1); + 0 0 0 2px rgba(62, 184, 255, 0.4), + 0 0 10px rgba(62, 184, 255, 0.3), + inset 0 0 20px rgba(62, 184, 255, 0.2); } } `, From aeac75fdc622185dbde27990b0fd91c13e232064 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Tue, 26 Nov 2024 22:24:31 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EC=98=A4=EB=B2=84=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scripts-lib/element-selector.ts | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 9eda282..43db58a 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -82,22 +82,24 @@ export class ElementSelector { setTimeout(() => (overlay.style.transform = 'scale(1)'), 200); }; - private handleClick = (e: MouseEvent) => { - if (this.status !== STATUS.SURFING) return; - + private handleMouseDown = (e: MouseEvent) => { const element = document.elementFromPoint(e.clientX, e.clientY); if (!element) return; + // 현재 선택된 요소와 새로 클릭한 요소가 다른 경우 선택 상태 해제 + if (this.selectedElement && this.selectedElement !== element) { + this.selectedElement = null; + this.setStatus(STATUS.SURFING); + return; + } + this.selectedElement = element; this.setStatus(STATUS.SELECTED); // 선택 효과 적용 - const overlay = document.getElementById('web_filter_overlay_element'); - if (overlay) { - const theme = overlayStyles.themes[this.currentTheme]; - overlay.style.animation = 'none'; - overlay.style.transform = 'scale(1)'; - overlay.style.border = '2px solid ' + this.getThemeColor(); + if (this.overlay) { + this.overlay.style.border = '2px solid ' + this.getThemeColor(); + // TODO: 여기서 팝업에서 정보 받을 수 있게 넘겨주기 } }; @@ -115,6 +117,17 @@ export class ElementSelector { } } + private handleScroll = () => { + // 요소 따라다니는 오버레이 위치 업데이트 + if (this.overlay && this.selectedElement) { + const rect = this.selectedElement.getBoundingClientRect(); + this.overlay.style.left = `${rect.left}px`; + this.overlay.style.top = `${rect.top}px`; + this.overlay.style.width = `${rect.width}px`; + this.overlay.style.height = `${rect.height}px`; + } + }; + private handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { this.cleanupOverlay(); @@ -124,8 +137,9 @@ export class ElementSelector { }; private bindEvents = () => { - window.addEventListener('click', this.handleClick); + window.addEventListener('scroll', this.handleScroll); window.addEventListener('keydown', this.handleKeyDown); + window.addEventListener('mousedown', this.handleMouseDown); window.addEventListener('mousemove', this.handleMouseMove); }; @@ -135,6 +149,7 @@ export class ElementSelector { * public methods */ public surfingElements = () => { + // TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 해야함 this.setStatus(STATUS.SURFING); }; @@ -164,12 +179,9 @@ export class ElementSelector { public destroy = () => { this.cleanupOverlay(); - window.removeEventListener('click', this.handleClick); + window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('keydown', this.handleKeyDown); + window.removeEventListener('mousedown', this.handleMouseDown); window.removeEventListener('mousemove', this.handleMouseMove); }; - - // TODO: mouseleave? 일 때 overlay 제거해주기 - // 다른 요소가 호버링되면, 감지되면 기존 Overlay 제거? - // 선택한 정보 넘기기 } From 634dc8bbf151b4ccb2e50460a50201e90e5737e2 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Tue, 26 Nov 2024 22:30:14 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20prevent=20event=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/scripts-lib/element-selector.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 43db58a..1a00806 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -25,6 +25,9 @@ export class ElementSelector { } private handleMouseMove = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (this.status !== STATUS.SURFING) return; const element = document.elementFromPoint(e.clientX, e.clientY); @@ -79,10 +82,13 @@ export class ElementSelector { private highlightElement = (overlay: HTMLElement) => { overlay.style.transform = 'scale(1.03)'; - setTimeout(() => (overlay.style.transform = 'scale(1)'), 200); + setTimeout(() => (overlay.style.transform = 'scale(1)'), 300); }; private handleMouseDown = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const element = document.elementFromPoint(e.clientX, e.clientY); if (!element) return; From f64e1ad1a8495495fb812c55655f115e9a94dfd3 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 08:12:56 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20action=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/types/status.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/projects/web-filter/src/types/status.ts b/projects/web-filter/src/types/status.ts index d177357..181a9a6 100644 --- a/projects/web-filter/src/types/status.ts +++ b/projects/web-filter/src/types/status.ts @@ -3,3 +3,8 @@ export enum STATUS { SURFING = 'SURFING', // 마우스가 움직이면서 요소를 탐색 중인 상태 SELECTED = 'SELECTED', // 요소가 선택된 상태 } + +export enum ACTION { + START_SELECT_ELEMENT = 'START_SELECT_ELEMENT', + APPLY_FILTER = 'APPLY_FILTER', +} From 314d21f76338e3521b158114c4c824d1316b0955 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 08:22:23 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20context=20tsx=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=B5=EA=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/content.ts | 18 --------- projects/web-filter/src/content.tsx | 39 +++++++++++++++++++ projects/web-filter/src/manifest-content.json | 2 +- projects/web-filter/src/manifest-native.json | 2 +- projects/web-filter/src/manifest.json | 2 +- 5 files changed, 42 insertions(+), 21 deletions(-) delete mode 100644 projects/web-filter/src/content.ts create mode 100644 projects/web-filter/src/content.tsx diff --git a/projects/web-filter/src/content.ts b/projects/web-filter/src/content.ts deleted file mode 100644 index bd09df5..0000000 --- a/projects/web-filter/src/content.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ElementSelector } from './scripts-lib/element-selector'; -import { STATUS } from './types/status'; - -const selector = new ElementSelector(); - -chrome.runtime.onMessage.addListener(async (request) => { - switch (request.action) { - case STATUS.INACTIVE: - break; - case STATUS.SURFING: - selector.surfingElements(); - break; - case STATUS.SELECTED: - break; - default: - break; - } -}); diff --git a/projects/web-filter/src/content.tsx b/projects/web-filter/src/content.tsx new file mode 100644 index 0000000..415ccc5 --- /dev/null +++ b/projects/web-filter/src/content.tsx @@ -0,0 +1,39 @@ +import { createRoot } from 'react-dom/client'; + +import { VintageFilter } from './filters/VintageFilter'; +import { WaveFilter } from './filters/WaveFilter'; +import { ElementSelector } from './scripts-lib/element-selector'; +import { ACTION } from './types/status'; + +// TODO: 탭 간 이동 시 초기화 +const selector = new ElementSelector(); + +chrome.runtime.onMessage.addListener(async (request) => { + switch (request.action) { + case ACTION.START_SELECT_ELEMENT: + selector.surfingElements(); + break; + case ACTION.APPLY_FILTER: + selector.applyFilter(request.filterId); + break; + default: + break; + } +}); + +export default function Content() { + return ( + + + + + + + ); +} + +const app = document.createElement('div'); +app.id = 'web-filter-app'; +app.style.display = 'none'; +document.body.appendChild(app); +createRoot(app).render(); diff --git a/projects/web-filter/src/manifest-content.json b/projects/web-filter/src/manifest-content.json index 6aeb6d5..ba2c6ab 100644 --- a/projects/web-filter/src/manifest-content.json +++ b/projects/web-filter/src/manifest-content.json @@ -15,7 +15,7 @@ "content_scripts": [ { "matches": [""], - "js": ["src/content.ts"] + "js": ["src/content.tsx"] } ] } diff --git a/projects/web-filter/src/manifest-native.json b/projects/web-filter/src/manifest-native.json index e9e8dee..bffa833 100644 --- a/projects/web-filter/src/manifest-native.json +++ b/projects/web-filter/src/manifest-native.json @@ -21,7 +21,7 @@ "content_scripts": [ { "matches": [""], - "js": ["src/content.ts"], + "js": ["src/content.tsx"], "all_frames": true } ], diff --git a/projects/web-filter/src/manifest.json b/projects/web-filter/src/manifest.json index 216653b..c18721b 100644 --- a/projects/web-filter/src/manifest.json +++ b/projects/web-filter/src/manifest.json @@ -21,7 +21,7 @@ "content_scripts": [ { "matches": [""], - "js": ["src/content.ts"] + "js": ["src/content.tsx"] } ] } From 834a890f16138982f5932dfdac94ad82cabfb8d1 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 08:22:35 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=EB=B9=88=ED=8B=B0=EC=A7=80=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/background.ts | 2 +- .../web-filter/src/filters/VintageFilter.tsx | 40 +++++++++++++++++ .../web-filter/src/filters/WaveFilter.tsx | 2 +- .../src/pages/PopupNative/PopupNative.tsx | 44 ++++++++++++++----- .../src/scripts-lib/element-selector.ts | 16 +++++-- 5 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 projects/web-filter/src/filters/VintageFilter.tsx diff --git a/projects/web-filter/src/background.ts b/projects/web-filter/src/background.ts index 55bdc02..0a51771 100644 --- a/projects/web-filter/src/background.ts +++ b/projects/web-filter/src/background.ts @@ -9,7 +9,7 @@ browser.runtime.onInstalled.addListener((details) => { chrome.runtime.onMessage.addListener((message, _, sendResponse) => { try { if (message.tabId && message.action) { - chrome.tabs.sendMessage(message.tabId, { action: message.action }); + chrome.tabs.sendMessage(message.tabId, { ...message }); } sendResponse({ success: true }); } catch (error) { diff --git a/projects/web-filter/src/filters/VintageFilter.tsx b/projects/web-filter/src/filters/VintageFilter.tsx new file mode 100644 index 0000000..c880d41 --- /dev/null +++ b/projects/web-filter/src/filters/VintageFilter.tsx @@ -0,0 +1,40 @@ +export function VintageFilter() { + return ( + + {/* 그레인 효과 추가 */} + + + + + {/* 원본 이미지의 투명도 조절 */} + + {/* 투명도 50% */} + + + + + ); +} diff --git a/projects/web-filter/src/filters/WaveFilter.tsx b/projects/web-filter/src/filters/WaveFilter.tsx index a3bf260..91008cb 100644 --- a/projects/web-filter/src/filters/WaveFilter.tsx +++ b/projects/web-filter/src/filters/WaveFilter.tsx @@ -1,7 +1,7 @@ export function WaveFilter() { return ( { @@ -9,7 +9,20 @@ export const PopupNative = () => { const tabId = tabs[0]?.id; if (tabId) { - await chrome.runtime.sendMessage({ tabId, action: STATUS.SURFING }); + await chrome.runtime.sendMessage({ + tabId, + action: ACTION.START_SELECT_ELEMENT, + }); + } + }; + + const handleFilterClick = async (filterId: string) => { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + const tabId = tabs[0]?.id; + const action = ACTION.APPLY_FILTER; + + if (tabId) { + await chrome.runtime.sendMessage({ tabId, action, filterId }); } }; @@ -19,14 +32,25 @@ export const PopupNative = () => {
- {['적록색맹', '필름카메라', '유리창', '빛 번짐', '기타'].map( - (filterName) => ( -
- -

{filterName}

-
- ), - )} + {[ + { + filterId: 'wave', + filterName: '파도', + }, + { + filterId: 'vintage', + filterName: '빈티지', + }, + ].map(({ filterId, filterName }) => ( +
handleFilterClick(filterId)} + > + +

{filterName}

+
+ ))}
diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 1a00806..3ffa05f 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -1,9 +1,10 @@ import { STATUS } from '../types/status'; import { overlayStyles } from './element-selector.styles'; +// TODO: 리스너 해제 연결, 초기화 export class ElementSelector { private status: STATUS = STATUS.INACTIVE; - private selectedElement: Element | null = null; + private selectedElement: HTMLElement | null = null; private styleSheet: HTMLStyleElement | null = null; private currentTheme: keyof typeof overlayStyles.themes = 'default'; private overlay: HTMLElement | null = null; @@ -24,6 +25,13 @@ export class ElementSelector { }); } + public applyFilter(filterId: string) { + if (!this.selectedElement) return; + + console.log(this.selectedElement); + this.selectedElement.style.filter = `url(#${filterId})`; + } + private handleMouseMove = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -81,7 +89,7 @@ export class ElementSelector { } private highlightElement = (overlay: HTMLElement) => { - overlay.style.transform = 'scale(1.03)'; + overlay.style.transform = 'scale(1.01)'; setTimeout(() => (overlay.style.transform = 'scale(1)'), 300); }; @@ -90,7 +98,7 @@ export class ElementSelector { e.stopPropagation(); const element = document.elementFromPoint(e.clientX, e.clientY); - if (!element) return; + if (!element || !(element instanceof HTMLElement)) return; // 현재 선택된 요소와 새로 클릭한 요소가 다른 경우 선택 상태 해제 if (this.selectedElement && this.selectedElement !== element) { @@ -155,7 +163,7 @@ export class ElementSelector { * public methods */ public surfingElements = () => { - // TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 해야함 + // TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 this.setStatus(STATUS.SURFING); }; From 0c6fc7d66cd61e6570c7665b9391e53e24c3dfb3 Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 17:57:56 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20overlay=20id=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scripts-lib/element-selector.ts | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 3ffa05f..3ca2501 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -2,7 +2,11 @@ import { STATUS } from '../types/status'; import { overlayStyles } from './element-selector.styles'; // TODO: 리스너 해제 연결, 초기화 +// TODO: selected element 정보 popup에 전달 +// TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 export class ElementSelector { + private OVERLAY_ID = 'web_filter_overlay_element'; + private status: STATUS = STATUS.INACTIVE; private selectedElement: HTMLElement | null = null; private styleSheet: HTMLStyleElement | null = null; @@ -15,21 +19,6 @@ export class ElementSelector { private setStatus(status: STATUS) { this.status = status; - this.notifyPopup(); - } - - private notifyPopup() { - chrome.runtime.sendMessage({ - status: this.status, - selectedElement: this.selectedElement ?? null, - }); - } - - public applyFilter(filterId: string) { - if (!this.selectedElement) return; - - console.log(this.selectedElement); - this.selectedElement.style.filter = `url(#${filterId})`; } private handleMouseMove = (e: MouseEvent) => { @@ -42,8 +31,7 @@ export class ElementSelector { if (!element) return; const overlay = - document.getElementById('web_filter_overlay_element') || - this.createOverlay(); + document.getElementById(this.OVERLAY_ID) || this.createOverlay(); const rect = element.getBoundingClientRect(); overlay.style.left = `${rect.left}px`; @@ -61,7 +49,7 @@ export class ElementSelector { } const overlay = document.createElement('div'); - overlay.id = 'web_filter_overlay_element'; + overlay.id = this.OVERLAY_ID; this.updateOverlayStyle(overlay); @@ -167,13 +155,16 @@ export class ElementSelector { this.setStatus(STATUS.SURFING); }; + public applyFilter(filterId: string) { + if (!this.selectedElement) return; + this.selectedElement.style.filter = `url(#${filterId})`; + } + public setTheme = (theme: keyof typeof overlayStyles.themes) => { this.currentTheme = theme; - const overlay = document.getElementById('web_filter_overlay_element'); + const overlay = document.getElementById(this.OVERLAY_ID); - if (overlay) { - this.updateOverlayStyle(overlay); - } + if (overlay) this.updateOverlayStyle(overlay); }; public cleanupOverlay = () => { From 5755fc8853bea824adfc9f05e271fba4db3d178d Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 17:59:09 +0900 Subject: [PATCH 13/14] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- projects/web-filter/src/scripts-lib/element-selector.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 3ca2501..25de9ad 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -4,6 +4,7 @@ import { overlayStyles } from './element-selector.styles'; // TODO: 리스너 해제 연결, 초기화 // TODO: selected element 정보 popup에 전달 // TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 +// TODO: 탭간 이동시 초기화 export class ElementSelector { private OVERLAY_ID = 'web_filter_overlay_element'; From dda2de515afad395e579a39d410dcba11b2fd73d Mon Sep 17 00:00:00 2001 From: lerrybe Date: Wed, 27 Nov 2024 19:25:42 +0900 Subject: [PATCH 14/14] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B0=A2=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/scripts-lib/element-selector.ts | 161 +++++++----------- .../utils/cleanup-overlay-element.ts | 12 ++ .../utils/create-overlay-element.ts | 6 + .../src/scripts-lib/utils/get-theme-color.ts | 14 ++ .../scripts-lib/utils/hightlight-element.ts | 4 + .../web-filter/src/scripts-lib/utils/index.ts | 16 ++ .../scripts-lib/utils/update-overlay-style.ts | 26 +++ 7 files changed, 138 insertions(+), 101 deletions(-) create mode 100644 projects/web-filter/src/scripts-lib/utils/cleanup-overlay-element.ts create mode 100644 projects/web-filter/src/scripts-lib/utils/create-overlay-element.ts create mode 100644 projects/web-filter/src/scripts-lib/utils/get-theme-color.ts create mode 100644 projects/web-filter/src/scripts-lib/utils/hightlight-element.ts create mode 100644 projects/web-filter/src/scripts-lib/utils/index.ts create mode 100644 projects/web-filter/src/scripts-lib/utils/update-overlay-style.ts diff --git a/projects/web-filter/src/scripts-lib/element-selector.ts b/projects/web-filter/src/scripts-lib/element-selector.ts index 25de9ad..c3eafa2 100644 --- a/projects/web-filter/src/scripts-lib/element-selector.ts +++ b/projects/web-filter/src/scripts-lib/element-selector.ts @@ -1,17 +1,25 @@ import { STATUS } from '../types/status'; -import { overlayStyles } from './element-selector.styles'; +import { + ThemeType, + cleanupOverlayElement, + createOverlayElement, + getThemeColor, + highlightElement, + updateOverlayStyle, +} from './utils'; // TODO: 리스너 해제 연결, 초기화 -// TODO: selected element 정보 popup에 전달 -// TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 -// TODO: 탭간 이동시 초기화 +// TODO: selected element 정보 popup에 전달 🚨 (for preview) +// TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 🚨 +// TODO: 탭간 이동시 초기화 🚨 + export class ElementSelector { private OVERLAY_ID = 'web_filter_overlay_element'; private status: STATUS = STATUS.INACTIVE; private selectedElement: HTMLElement | null = null; private styleSheet: HTMLStyleElement | null = null; - private currentTheme: keyof typeof overlayStyles.themes = 'default'; + private currentTheme: ThemeType = 'default'; private overlay: HTMLElement | null = null; constructor() { @@ -22,6 +30,47 @@ export class ElementSelector { this.status = status; } + private createOverlay = () => { + // 기존 오버레이 제거 + if (this.overlay && document.body.contains(this.overlay)) { + document.body.removeChild(this.overlay); + } + + const overlay = createOverlayElement(this.OVERLAY_ID); + this.styleSheet = updateOverlayStyle( + overlay, + this.currentTheme, + this.styleSheet, + ); + + this.overlay = overlay; + return overlay; + }; + + /** + * @description + * + * event handlers + */ + + private handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + this.cleanupOverlay(); + this.setStatus(STATUS.INACTIVE); + this.selectedElement = null; + } + }; + + private handleScroll = () => { + if (this.overlay && this.selectedElement) { + const rect = this.selectedElement.getBoundingClientRect(); + this.overlay.style.left = `${rect.left}px`; + this.overlay.style.top = `${rect.top}px`; + this.overlay.style.width = `${rect.width}px`; + this.overlay.style.height = `${rect.height}px`; + } + }; + private handleMouseMove = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -40,46 +89,7 @@ export class ElementSelector { overlay.style.width = `${rect.width}px`; overlay.style.height = `${rect.height}px`; - this.highlightElement(overlay); - }; - - private createOverlay = () => { - // 기존 오버레이 제거 - if (this.overlay && document.body.contains(this.overlay)) { - document.body.removeChild(this.overlay); - } - - const overlay = document.createElement('div'); - overlay.id = this.OVERLAY_ID; - - this.updateOverlayStyle(overlay); - - document.body.appendChild(overlay); - this.overlay = overlay; - return overlay; - }; - - private updateOverlayStyle(overlay: HTMLElement) { - // 기존 스타일시트 제거 - if (this.styleSheet && document.head.contains(this.styleSheet)) { - document.head.removeChild(this.styleSheet); - } - - const theme = overlayStyles.themes[this.currentTheme]; - - // 새 스타일시트 생성 및 적용 - this.styleSheet = document.createElement('style'); - this.styleSheet.textContent = theme.keyframes; - document.head.appendChild(this.styleSheet); - - // 오버레이에 스타일 적용 - overlay.style.cssText = overlayStyles.base + theme.style; - overlay.style.animation = 'pulse 2s infinite'; - } - - private highlightElement = (overlay: HTMLElement) => { - overlay.style.transform = 'scale(1.01)'; - setTimeout(() => (overlay.style.transform = 'scale(1)'), 300); + highlightElement(overlay); }; private handleMouseDown = (e: MouseEvent) => { @@ -89,7 +99,6 @@ export class ElementSelector { const element = document.elementFromPoint(e.clientX, e.clientY); if (!element || !(element instanceof HTMLElement)) return; - // 현재 선택된 요소와 새로 클릭한 요소가 다른 경우 선택 상태 해제 if (this.selectedElement && this.selectedElement !== element) { this.selectedElement = null; this.setStatus(STATUS.SURFING); @@ -99,43 +108,9 @@ export class ElementSelector { this.selectedElement = element; this.setStatus(STATUS.SELECTED); - // 선택 효과 적용 if (this.overlay) { - this.overlay.style.border = '2px solid ' + this.getThemeColor(); - // TODO: 여기서 팝업에서 정보 받을 수 있게 넘겨주기 - } - }; - - private getThemeColor(): string { - // 테마별 주요 색상 반환 - switch (this.currentTheme) { - case 'pastel': - return 'rgba(255, 182, 193, 0.5)'; - case 'neon': - return '#00ff00'; - case 'minimal': - return 'rgba(0, 0, 0, 0.2)'; - default: - return 'rgba(62, 184, 255, 0.5)'; - } - } - - private handleScroll = () => { - // 요소 따라다니는 오버레이 위치 업데이트 - if (this.overlay && this.selectedElement) { - const rect = this.selectedElement.getBoundingClientRect(); - this.overlay.style.left = `${rect.left}px`; - this.overlay.style.top = `${rect.top}px`; - this.overlay.style.width = `${rect.width}px`; - this.overlay.style.height = `${rect.height}px`; - } - }; - - private handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - this.cleanupOverlay(); - this.setStatus(STATUS.INACTIVE); - this.selectedElement = null; + this.overlay.style.border = + '2px solid ' + getThemeColor(this.currentTheme); } }; @@ -152,7 +127,6 @@ export class ElementSelector { * public methods */ public surfingElements = () => { - // TODO: 화면에서 기존 요소와의 인터랙션을 불가능하게 하기 this.setStatus(STATUS.SURFING); }; @@ -161,25 +135,10 @@ export class ElementSelector { this.selectedElement.style.filter = `url(#${filterId})`; } - public setTheme = (theme: keyof typeof overlayStyles.themes) => { - this.currentTheme = theme; - const overlay = document.getElementById(this.OVERLAY_ID); - - if (overlay) this.updateOverlayStyle(overlay); - }; - public cleanupOverlay = () => { - // 스타일시트 제거 - if (this.styleSheet && document.head.contains(this.styleSheet)) { - document.head.removeChild(this.styleSheet); - this.styleSheet = null; - } - - // 오버레이 제거 - if (this.overlay && document.body.contains(this.overlay)) { - document.body.removeChild(this.overlay); - this.overlay = null; - } + cleanupOverlayElement(this.styleSheet, this.overlay); + this.styleSheet = null; + this.overlay = null; }; public destroy = () => { diff --git a/projects/web-filter/src/scripts-lib/utils/cleanup-overlay-element.ts b/projects/web-filter/src/scripts-lib/utils/cleanup-overlay-element.ts new file mode 100644 index 0000000..70cfbdf --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/cleanup-overlay-element.ts @@ -0,0 +1,12 @@ +export const cleanupOverlayElement = ( + styleSheet: HTMLStyleElement | null, + overlay: HTMLElement | null, +): void => { + if (styleSheet && document.head.contains(styleSheet)) { + document.head.removeChild(styleSheet); + } + + if (overlay && document.body.contains(overlay)) { + document.body.removeChild(overlay); + } +}; diff --git a/projects/web-filter/src/scripts-lib/utils/create-overlay-element.ts b/projects/web-filter/src/scripts-lib/utils/create-overlay-element.ts new file mode 100644 index 0000000..85a8ce6 --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/create-overlay-element.ts @@ -0,0 +1,6 @@ +export const createOverlayElement = (overlayId: string): HTMLElement => { + const overlay = document.createElement('div'); + overlay.id = overlayId; + document.body.appendChild(overlay); + return overlay; +}; diff --git a/projects/web-filter/src/scripts-lib/utils/get-theme-color.ts b/projects/web-filter/src/scripts-lib/utils/get-theme-color.ts new file mode 100644 index 0000000..dc06803 --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/get-theme-color.ts @@ -0,0 +1,14 @@ +import { ThemeType } from '.'; + +export const getThemeColor = (currentTheme: ThemeType): string => { + switch (currentTheme) { + case 'pastel': + return 'rgba(255, 182, 193, 0.5)'; + case 'neon': + return '#00ff00'; + case 'minimal': + return 'rgba(0, 0, 0, 0.2)'; + default: + return 'rgba(62, 184, 255, 0.5)'; + } +}; diff --git a/projects/web-filter/src/scripts-lib/utils/hightlight-element.ts b/projects/web-filter/src/scripts-lib/utils/hightlight-element.ts new file mode 100644 index 0000000..1ccb57a --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/hightlight-element.ts @@ -0,0 +1,4 @@ +export const highlightElement = (overlay: HTMLElement): void => { + overlay.style.transform = 'scale(1.01)'; + setTimeout(() => (overlay.style.transform = 'scale(1)'), 300); +}; diff --git a/projects/web-filter/src/scripts-lib/utils/index.ts b/projects/web-filter/src/scripts-lib/utils/index.ts new file mode 100644 index 0000000..3aa1858 --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/index.ts @@ -0,0 +1,16 @@ +import { overlayStyles } from '../element-selector.styles'; +import { cleanupOverlayElement } from './cleanup-overlay-element'; +import { createOverlayElement } from './create-overlay-element'; +import { getThemeColor } from './get-theme-color'; +import { highlightElement } from './hightlight-element'; +import { updateOverlayStyle } from './update-overlay-style'; + +export type ThemeType = keyof typeof overlayStyles.themes; + +export { + createOverlayElement, + cleanupOverlayElement, + highlightElement, + updateOverlayStyle, + getThemeColor, +}; diff --git a/projects/web-filter/src/scripts-lib/utils/update-overlay-style.ts b/projects/web-filter/src/scripts-lib/utils/update-overlay-style.ts new file mode 100644 index 0000000..8dd302f --- /dev/null +++ b/projects/web-filter/src/scripts-lib/utils/update-overlay-style.ts @@ -0,0 +1,26 @@ +import { ThemeType } from '.'; +import { overlayStyles } from '../element-selector.styles'; + +export const updateOverlayStyle = ( + overlay: HTMLElement, + currentTheme: ThemeType, + styleSheet: HTMLStyleElement | null, +): HTMLStyleElement => { + // 기존 스타일시트 제거 + if (styleSheet && document.head.contains(styleSheet)) { + document.head.removeChild(styleSheet); + } + + const theme = overlayStyles.themes[currentTheme]; + + // 새 스타일시트 생성 및 적용 + const newStyleSheet = document.createElement('style'); + newStyleSheet.textContent = theme.keyframes; + document.head.appendChild(newStyleSheet); + + // 오버레이에 스타일 적용 + overlay.style.cssText = overlayStyles.base + theme.style; + overlay.style.animation = 'pulse 2s infinite'; + + return newStyleSheet; +};