Skip to content
Draft
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
1 change: 1 addition & 0 deletions projects/web-filter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions projects/web-filter/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, { ...message });
}
sendResponse({ success: true });
} catch (error) {
sendResponse({ success: false, error });
}
});
7 changes: 5 additions & 2 deletions projects/web-filter/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<button className={`${styles.Button} ${styles[theme]}`}>{children}</button>
<button className={`${styles.Button} ${styles[theme]}`} onClick={onClick}>
{children}
</button>
);
};
40 changes: 38 additions & 2 deletions projects/web-filter/src/content.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
import { popup } from './functions/popup';
import { createRoot } from 'react-dom/client';

popup.open();
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 (
<svg>
<defs>
<WaveFilter />
<VintageFilter />
</defs>
</svg>
);
}

const app = document.createElement('div');
app.id = 'web-filter-app';
app.style.display = 'none';
document.body.appendChild(app);
createRoot(app).render(<Content />);
40 changes: 40 additions & 0 deletions projects/web-filter/src/filters/VintageFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export function VintageFilter() {
return (
<filter
id="vintage"
x="0"
y="0"
width="100%"
height="100%"
filterUnits="objectBoundingBox"
>
{/* 그레인 효과 추가 */}
<feTurbulence
type="fractalNoise"
baseFrequency="0.8"
numOctaves="2"
stitchTiles="stitch"
result="noise"
/>

<feColorMatrix
type="saturate"
values="0"
in="noise"
result="grayscaleNoise"
/>

{/* 원본 이미지의 투명도 조절 */}
<feComponentTransfer in="SourceGraphic" result="transparent">
<feFuncA type="linear" slope="0.5" /> {/* 투명도 50% */}
</feComponentTransfer>

<feBlend
mode="overlay"
in="transparent"
in2="grayscaleNoise"
result="grainApplied"
/>
</filter>
);
}
2 changes: 1 addition & 1 deletion projects/web-filter/src/filters/WaveFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function WaveFilter() {
return (
<filter
id="filter"
id="wave"
x="-20%"
y="-20%"
width="140%"
Expand Down
11 changes: 10 additions & 1 deletion projects/web-filter/src/manifest-native.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@
"background": {
"{{chrome}}.service_worker": "src/background.ts",
"{{firefox}}.scripts": ["src/background.ts"]
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content.tsx"],
"all_frames": true
}
],
"permissions": ["activeTab", "tabs"],
"host_permissions": ["<all_urls>"]
}
54 changes: 45 additions & 9 deletions projects/web-filter/src/pages/PopupNative/PopupNative.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,64 @@
import { Button } from '../../components/Button/Button';
import { Header } from '../../components/Header/Header';
import { ACTION } 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: 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 });
}
};

return (
<article className={styles.PopupNative}>
<Header />
<img src="/sample-image.png" />
<div className={styles.slider}>
<div className={styles.filterList}>
{['적록색맹', '필름카메라', '유리창', '빛 번짐', '기타'].map(
(filterName) => (
<div key={filterName} className={styles.filterItem}>
<img src="/sample-image.png" />
<h3 className={styles.filterName}>{filterName}</h3>
</div>
),
)}
{[
{
filterId: 'wave',
filterName: '파도',
},
{
filterId: 'vintage',
filterName: '빈티지',
},
].map(({ filterId, filterName }) => (
<div
key={filterId}
className={styles.filterItem}
onClick={() => handleFilterClick(filterId)}
>
<img src="/sample-image.png" />
<h3 className={styles.filterName}>{filterName}</h3>
</div>
))}
</div>
</div>
<div className={styles.divider} />
<footer className={styles.footer}>
<Button theme="gray">필터 초기화</Button>
<Button>적용하기</Button>
<Button theme="blue" onClick={handleStartSelectElement}>
요소 선택하기
</Button>
</footer>
</article>
);
Expand Down
134 changes: 134 additions & 0 deletions projects/web-filter/src/scripts-lib/element-selector.styles.ts
Original file line number Diff line number Diff line change
@@ -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.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.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.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.4),
0 0 10px rgba(62, 184, 255, 0.3),
inset 0 0 20px rgba(62, 184, 255, 0.2);
}
}
`,
},

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;
Loading