Skip to content

Commit 2f0b1f2

Browse files
committed
refactor(architecture): restructure UI and align build pipeline
- move popup into ui/popup (index.html, index.ts) - move settings dashboard into ui/settings (index.html, index.ts) - split overlay/index.ts into controller and persistence modules - update esbuild config to reflect new UI structure - update internal import paths to match new structure
1 parent e0c127d commit 2f0b1f2

11 files changed

Lines changed: 146 additions & 127 deletions

File tree

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,34 @@ src/
138138
│ ├── messenger.ts # content - background message bus
139139
│ └── storage/ # Settings, stats, sessions, and security storage
140140
├── platforms/
141-
│ ├── youtube/index.ts # MutationObserver on URL, 1.5s watch threshold
141+
│ ├── youtube/index.ts # YouTube adapter (Videos detection + tracking)
142+
│ └── generic/index.ts # Fallback adapter
142143
├── ui/
143-
│ └── overlay/
144-
│ ├── index.ts # Reads storage + i18n, builds overlay data
145-
│ └── renderer.ts # Pure DOM builder, countdown timer
144+
│ ├── popup/
145+
│ │ ├── index.html # Extension popup UI
146+
│ │ └── index.ts # Popup logic
147+
│ │
148+
│ ├── settings/
149+
│ │ ├── index.html # Options page (settings UI)
150+
│ │ └── index.ts # Settings logic
151+
│ │
152+
│ ├── overlay/
153+
│ │ ├── controller.ts # Orchestrates overlay (data + events)
154+
│ │ ├── persistence.ts # Ensures overlay cannot be removed (observer)
155+
│ │ └── renderer.ts # Pure DOM builder + countdown timer
156+
│ │
157+
│ └── components/ # Reusable UI components (custom controls)
146158
├── i18n/
147159
│ ├── types.ts # LocaleStrings interface (type-safe translations)
148160
│ ├── index.ts # i18n singleton with locale switching
149161
│ └── locales/
150162
│ ├── en.ts
151163
│ └── ar.ts
152-
├── settings/
153-
│ ├── dashboard.html # Options page
154-
│ └── dashboard.ts # Dashboard logic
155-
├── background.ts # Alarm-based time limit checker (read-only for time)
164+
├── _locales/ # Chrome extension translations (manifest-level)
165+
├── assets/ # Icons and static assets
166+
├── background.ts # Alarm-based time limit checker (background worker)
156167
├── content.ts # Entry point, adapter selection
157-
├── popup.ts # Popup UI logic
158-
└── types.ts # PlatformAdapter, AppAction, ExtensionMessage
168+
└── types.ts # Shared types (AppAction, messages, adapters)
159169
```
160170

161171
## Getting Started

esbuild.config.mjs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ const common = {
4242

4343
const context = await esbuild.context({
4444
...common,
45-
entryPoints: ['src/content.ts', 'src/popup.ts', 'src/background.ts', 'src/settings/dashboard.ts'],
45+
entryPoints: [
46+
'src/content.ts',
47+
'src/background.ts',
48+
'src/ui/popup/index.ts',
49+
'src/ui/settings/index.ts',
50+
],
4651
outdir: 'dist',
4752
plugins: [
4853
{
@@ -52,8 +57,8 @@ const context = await esbuild.context({
5257
build.onEnd(() => {
5358
copyFileToDir('manifest.json', 'dist');
5459
copyFileToDir('styles.css', 'dist');
55-
copyFileToDir('src/popup.html', 'dist');
56-
copyFileToDir('src/settings/dashboard.html', 'dist/settings');
60+
copyFileToDir('src/ui/popup/index.html', 'dist/ui/popup');
61+
copyFileToDir('src/ui/settings/index.html', 'dist/ui/settings');
5762
copyDirRecursive('src/_locales', 'dist/_locales');
5863
copyDirRecursive('src/assets', 'dist/assets');
5964
console.log('[+] Build finished');
@@ -64,8 +69,8 @@ const context = await esbuild.context({
6469

6570
const filesToWatch = [
6671
{ src: 'styles.css', dest: 'dist' },
67-
{ src: 'src/popup.html', dest: 'dist' },
68-
{ src: 'src/settings/dashboard.html', dest: 'dist/settings' },
72+
{ src: 'src/ui/popup/index.html', dest: 'dist/ui/popup' },
73+
{ src: 'src/ui/settings/index.html', dest: 'dist/ui/settings' },
6974
];
7075

7176
filesToWatch.forEach(({ src, dest }) => {

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"128": "assets/manifest/128x128.png"
1818
},
1919
"action": {
20-
"default_popup": "popup.html",
20+
"default_popup": "ui/popup/index.html",
2121
"default_icon": {
2222
"16": "assets/manifest/16x16.png",
2323
"32": "assets/manifest/32x32.png",
@@ -26,7 +26,7 @@
2626
}
2727
},
2828
"options_ui": {
29-
"page": "settings/dashboard.html",
29+
"page": "ui/settings/index.html",
3030
"open_in_tab": true
3131
},
3232
"content_scripts": [

src/app/orchestrator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Tracker } from '../core/tracker';
22
import { Limiter } from '../core/limiter';
33
import { Messenger } from '../core/messenger';
44
import { SessionManager } from '../core/session';
5-
import { showOverlay } from '../ui/overlay/index';
5+
import { showOverlay, initOverlayListeners } from '../ui/overlay/controller';
66
import { storage } from '../core/storage/index';
77
import { PlatformAdapter } from '../types';
88
// import { GenericAdapter } from './platforms/generic/index';
@@ -17,6 +17,8 @@ export class AppOrchestrator {
1717
}
1818

1919
public async start() {
20+
initOverlayListeners();
21+
2022
const safeBlock = async (reason: string = 'time') => {
2123
if (!this.isBlocked) {
2224
this.isBlocked = true;

src/ui/overlay/controller.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { storage } from '../../core/storage';
2+
import { i18n } from '../../i18n/index';
3+
import { renderOverlay, updateOverlayTheme } from './renderer';
4+
import { watchOverlayPersistence } from './persistence';
5+
6+
/**
7+
* Main entry to show overlay
8+
*/
9+
export async function showOverlay(reasonKey: string = 'time'): Promise<void> {
10+
if (document.getElementById('limitra-overlay')) return;
11+
12+
await i18n.init();
13+
14+
const toneKey = await storage.getQuoteTone();
15+
const quoteText = i18n.getRandomQuote(toneKey);
16+
17+
const reasonText =
18+
reasonKey === 'count'
19+
? i18n.t.popup.reason_count
20+
: reasonKey === 'bypass'
21+
? i18n.t.popup.reason_bypass
22+
: i18n.t.popup.reason_time;
23+
24+
const badgeText = i18n.t.overlay.badgeBlocked;
25+
const unlocksInText = i18n.t.overlay.unlocksIn;
26+
const unlockingText = i18n.t.overlay.unlocking;
27+
const clickToCopyText = i18n.t.overlay.clickToCopy;
28+
const copiedText = i18n.t.overlay.copiedText;
29+
const direction = i18n.language === 'ar' ? 'rtl' : 'ltr';
30+
31+
const savedTheme = await storage.getTheme();
32+
const isDark =
33+
savedTheme === 'dark' ||
34+
(savedTheme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
35+
36+
const nextResetTime = await storage.getNextResetTime();
37+
38+
renderOverlay({
39+
badgeText,
40+
quoteText,
41+
reasonText,
42+
isDark,
43+
direction,
44+
nextResetTime,
45+
unlocksInText,
46+
unlockingText,
47+
clickToCopyText,
48+
copiedText,
49+
});
50+
51+
watchOverlayPersistence(() => {
52+
void showOverlay('bypass');
53+
});
54+
}
55+
56+
/**
57+
* Setup listeners (call once)
58+
*/
59+
export function initOverlayListeners(): void {
60+
chrome.storage.onChanged.addListener((changes, namespace) => {
61+
if (namespace === 'local' && changes['limitra_theme']) {
62+
const theme = changes['limitra_theme'].newValue as string;
63+
const isDark =
64+
theme === 'dark' ||
65+
(theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
66+
67+
updateOverlayTheme(isDark);
68+
}
69+
});
70+
71+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', async () => {
72+
const currentTheme = await storage.getTheme();
73+
if (currentTheme === 'auto') {
74+
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
75+
updateOverlayTheme(isDark);
76+
}
77+
});
78+
}

src/ui/overlay/index.ts

Lines changed: 0 additions & 93 deletions
This file was deleted.

src/ui/overlay/persistence.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
let isOverlayObserverRunning = false;
2+
3+
export function watchOverlayPersistence(onRemoved: () => void): void {
4+
if (isOverlayObserverRunning) return;
5+
isOverlayObserverRunning = true;
6+
7+
const observer = new MutationObserver(() => {
8+
if (!document.getElementById('limitra-overlay')) {
9+
observer.disconnect();
10+
isOverlayObserverRunning = false;
11+
12+
onRemoved();
13+
}
14+
});
15+
16+
observer.observe(document.body, { childList: true });
17+
}

src/popup.html renamed to src/ui/popup/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@700&display=swap"
77
rel="stylesheet"
88
/>
9-
<link rel="stylesheet" href="styles.css" />
9+
<link rel="stylesheet" href="../../styles.css" />
1010
</head>
1111
<body>
1212
<div class="popup-container">
@@ -165,6 +165,6 @@ <h3 id="app-title">Limitra</h3>
165165
</div>
166166
</div>
167167

168-
<script src="popup.js"></script>
168+
<script src="index.js"></script>
169169
</body>
170170
</html>

src/popup.ts renamed to src/ui/popup/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { storage } from './core/storage';
2-
import { i18n } from './i18n/index';
3-
import { AppAction } from './types';
1+
import { storage } from '../../core/storage';
2+
import { i18n } from '../../i18n/index';
3+
import { AppAction } from '../../types';
44

55
const limitInput = document.getElementById('limit') as HTMLInputElement;
66
const timeInput = document.getElementById('timeLimit') as HTMLInputElement;
@@ -157,8 +157,8 @@ function applyTheme(theme: string) {
157157

158158
function updateActionIcon(isSystemDark: boolean) {
159159
const iconPath = isSystemDark
160-
? 'assets/manifest/32x32-dark.png'
161-
: 'assets/manifest/32x32-light.png';
160+
? '../../assets/manifest/32x32-dark.png'
161+
: '../../assets/manifest/32x32-light.png';
162162

163163
if (chrome.action) {
164164
void chrome.action
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,25 @@
44
<meta charset="UTF-8" />
55
<title>Limitra Dashboard</title>
66

7-
<link rel="icon" type="image/svg+xml" href="../assets/manifest/32x32.png" />
7+
<link rel="icon" type="image/svg+xml" href="../../assets/manifest/32x32.png" />
88
<link
99
rel="icon"
1010
type="image/svg+xml"
11-
href="../assets/manifest/32x32-light.png"
11+
href="../../assets/manifest/32x32-light.png"
1212
media="(prefers-color-scheme: light)"
1313
/>
1414
<link
1515
rel="icon"
1616
type="image/svg+xml"
17-
href="../assets/manifest/32x32-dark.png"
17+
href="../../assets/manifest/32x32-dark.png"
1818
media="(prefers-color-scheme: dark)"
1919
/>
2020

2121
<link
2222
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@700&display=swap"
2323
rel="stylesheet"
2424
/>
25-
<link rel="stylesheet" href="../styles.css" />
25+
<link rel="stylesheet" href="../../styles.css" />
2626
</head>
2727
<body>
2828
<div class="dashboard-header">
@@ -285,6 +285,6 @@ <h2 id="title-about" class="about-title">About Limitra</h2>
285285
</div>
286286
</div>
287287
</div>
288-
<script src="dashboard.js"></script>
288+
<script src="index.js"></script>
289289
</body>
290290
</html>

0 commit comments

Comments
 (0)