diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d871d82..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d35654..695829a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,35 +16,27 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Use Node.js 18.x + - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '18.x' cache: 'npm' + - name: Cache node_modules + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Install dependencies run: npm ci - name: Lint (ESLint) run: npm run lint - # We dont have any test yet - # - name: Run tests (Jest) + # What tests ! + # - name: Run Tests # run: npm test - - - name: Build (Webpack) - run: npm run build - - # This doesnt really work yet we need to figure this out - # - name: Package Firefox Extension - # run: npm run build:ext - - - name: Generate docs (JSDoc) - run: npm run docs - - # This doesnt work yet need to figure this out - # - name: Upload extension artifact - # uses: actions/upload-artifact@v3 - # with: - # name: firefox-recap-extension - # path: web-ext-artifacts/*.zip \ No newline at end of file + diff --git a/.gitignore b/.gitignore index 2d6fd38..887de55 100644 --- a/.gitignore +++ b/.gitignore @@ -705,3 +705,4 @@ fabric.properties # Exclude web-ext build artifacts /web-ext-artifacts/ /*.zip +.DS_Store diff --git a/package.json b/package.json index b07ab3d..4b91ac0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dev": "webpack --mode development", "build": "webpack --mode production", "test": "jest --env=jsdom", - "build:ext": "npm run build && web-ext build --source-dir ./dist/" + "build:ext": "npm run build && web-ext build --overwrite-dest --source-dir ./dist/" }, "repository": { "type": "git", diff --git a/src/background/background.js b/src/background/background.js index c35dab9..62b5c2a 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -8,7 +8,23 @@ */ import { initDB } from './initdb.js'; -import handlers from './handlers/index.js'; +import handlers from './handlers/index.js'; // Keep the default import for window assignment + +// Destructure the specific handlers needed for the message listener +const { + fetchAndStoreHistory, + getMostVisitedSites, + getVisitsPerHour, + getLabelCounts, + //getTimeSpentPerSite, + getCategoryTrends, + getCOCounts, + getDailyVisitCounts, + getRecencyFrequency, + getTransitionPatterns, + getUniqueWebsites +} = handlers; + /** * Initialize the extension’s database on startup. @@ -54,10 +70,11 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { return true; } - if (action === "getTimeSpentPerSite") { - getTimeSpentPerSite(days, limit).then(sendResponse); - return true; - } + // Note: 'getTimeSpentPerSite' is not defined in handlers/index.js + // if (action === "getTimeSpentPerSite") { + // getTimeSpentPerSite(days, limit).then(sendResponse); + // return true; + // } if (action === "getCategoryTrends") { getCategoryTrends(days).then(sendResponse); @@ -91,19 +108,5 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { console.warn("[Background] No handler for action:", action); sendResponse(null); - return true; + return true; // Keep true here for async sendResponse }); - - -/** - * Expose background handler functions on the global `window` object. - * - * This allows you to call e.g. - * ``` - * getMostVisitedSites(7).then(console.log) - * ``` - * directly from the console for debugging or ad‐hoc testing. - * - * @type {Object.} - */ -Object.assign(window, handlers); diff --git a/src/background/services/ml.js b/src/background/services/ml.js index 16f5e25..33354f8 100644 --- a/src/background/services/ml.js +++ b/src/background/services/ml.js @@ -110,7 +110,7 @@ export async function classifyURLAndTitle( console.log('ML classify:', textToClassify); const result = await mlApi.runEngine({ args: [textToClassify], - options: { top_k: null }, + options: { top_k: null }, // mutli-label classification we apply threshold later this might be better at 2 }); const mapped = result diff --git a/src/manifest.json b/src/manifest.json index 4a7d369..ae7e559 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Firefox Recap", - "version": "2.0.0", + "version": "1.0.0", "description": "Categorize and analyze browsing history for productivity insights.", "permissions": [ "history", @@ -18,7 +18,7 @@ "scripts": [ "background.js" ], - "persistent": false + "persistent": true }, "browser_action": { "default_popup": "popup.html", @@ -32,7 +32,13 @@ "128": "assets/icon128.png" }, "web_accessible_resources": [ - "recap.html" + "recap.html", + "assets/videos/*.mp4" ], + "browser_specific_settings": { + "gecko": { + "id": "firefoxrecap@gmail.com" + } + }, "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; connect-src https://big.oisd.nl blob:;" } \ No newline at end of file diff --git a/src/popup/SlideShow.jsx b/src/popup/SlideShow.jsx index 3f4fe73..49dffbe 100644 --- a/src/popup/SlideShow.jsx +++ b/src/popup/SlideShow.jsx @@ -4,6 +4,7 @@ import { FaArrowRight, FaArrowLeft } from 'react-icons/fa'; import promptsData from "./prompts.json"; import RadarCategoryChart from './RadarCategoryChart'; import TimeOfDayHistogram from './TimeOfDayHistogram'; +import WavyText from './WavyText'; import CategoryTrendsLineChart from './CategoryTrendsLineChart'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer as LineContainer } from 'recharts'; @@ -17,11 +18,13 @@ const safeCallBackground = async (action, payload = {}) => { } }; + const SlideShow = ({ setView, timeRange }) => { const [slides, setSlides] = useState([]); const [index, setIndex] = useState(0); const [loading, setLoading] = useState(true); const [progress, setProgress] = useState(0); + const [notEnoughData, setNotEnoughData] = useState(false); const videoRef = useRef(null); const backgroundVideos = [ @@ -53,7 +56,7 @@ const SlideShow = ({ setView, timeRange }) => { return array; }; - useEffect(() => { + useEffect(() => { const loadSlides = async () => { setLoading(true); const daysMap = { day: 1, week: 7, month: 30 }; @@ -81,6 +84,16 @@ const SlideShow = ({ setView, timeRange }) => { }); const totalUnique = await safeCallBackground("getUniqueWebsites", { days }); + + // see if theres any data, if not skip slides + if (!totalUnique || totalUnique === 0) { + console.log("[SlideShow] Not enough data (totalUnique=0)."); + setNotEnoughData(true); + setLoading(false); + setProgress(100); + return; + } + slides.push({ id: 'totalWebsites', video: videos[2], @@ -179,12 +192,12 @@ const SlideShow = ({ setView, timeRange }) => { video: videos[7], prompt: pickPrompt("recapOutro", { x: timeRangeMap[timeRange] }) }); - setSlides(slides); + setNotEnoughData(false); setLoading(false); setProgress(100); }; - + loadSlides(); }, [timeRange]); @@ -207,42 +220,37 @@ const SlideShow = ({ setView, timeRange }) => { }, [index]); useEffect(() => { + if (loading || notEnoughData) return; + const timer = setTimeout(() => { setIndex(prev => (prev < slides.length - 1 ? prev + 1 : prev)); }, 5000); + return () => clearTimeout(timer); - }, [index, slides.length]); + }, [index, slides.length, loading, notEnoughData]); - // 🚀 LOADING SCREEN while slides are being fetched + // LOADING SCREEN if (loading || progress < 100) { return ( -
-

Preparing your recap...

-
-
+
+
+ +
+
+
); } + + if (notEnoughData) { + return ( +
+

Not enough browsing history yet. Your recap will be ready once you’ve explored a bit more!

+
+ ); + } + // 🚀 SLIDESHOW UI after loading return ( @@ -251,8 +259,6 @@ const SlideShow = ({ setView, timeRange }) => { {slides[index]?.video && } - -
{slides[index]?.chart ? ( <> diff --git a/src/popup/WavyText.jsx b/src/popup/WavyText.jsx new file mode 100644 index 0000000..534a89b --- /dev/null +++ b/src/popup/WavyText.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import './popup.css'; + + +const WavyText = ({ text }) => { + return ( +
+ {text.split(/(\s+)/).map((segment, index) => { + if (segment.trim() === '') { + // Render spaces without animation + return {segment}; + } else { + // Render each character in the word with animation + return ( + + {segment.split('').map((char, charIndex) => ( + + {char} + + ))} + + ); + } + })} +
+ ); + }; + + export default WavyText; \ No newline at end of file diff --git a/src/popup/popup.css b/src/popup/popup.css index b704a03..831d467 100644 --- a/src/popup/popup.css +++ b/src/popup/popup.css @@ -119,7 +119,7 @@ body { padding: 15px; font-size: 30px; background-color: #6F84B3; - color: white; + color: #fff; border: none; margin-bottom: 15px; border-radius: 15px; @@ -137,27 +137,62 @@ body { button:disabled { display: none; } - -.spinner-overlay { - position: fixed; +/* Container that covers the entire viewport */ +.loading-screen { + position: absolute; top: 0; left: 0; - width: 100vw; - height: 100vh; - background-color: black; - opacity: 20%; + width: 100%; + height: 100%; + background-color: black; /* Optional: set background color */ display: flex; - align-items: center; justify-content: center; - z-index: 9999; /* ensures it stays on top */ + align-items: center; +} + +/* Container that centers its content vertically */ +.center-container { + display: flex; + flex-direction: column; + align-items: center; +} + +/* Wavy text styling */ +.wavy-text { + display: inline-block; + font-size: 2rem; + font-weight: bold; + color: white; + text-align: center; + margin-bottom: 20px; /* Space between text and progress bar */ +} + +.wavy-char { + display: inline-block; + animation: wave 1.5s infinite; + animation-delay: calc(0.1s * var(--i)); +} + +@keyframes wave { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +/* Progress bar styling */ +.progress-bar { + width: 100%; + height: 8px; + background-color: #555; + border-radius: 5px; + overflow: hidden; } -.spinner { - border: 6px solid #fff; - border-top: 6px solid #444; - border-radius: 50%; - width: 60px; - height: 60px; - animation: spin 1s linear infinite; +.progress-bar-fill { + height: 100%; + background-color: #00C853; + transition: width 0.5s ease-in-out; } -@keyframes spin { to { transform: rotate(360deg); } } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 9493fd3..1cb3f85 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -30,8 +30,10 @@ module.exports = { plugins: [ new CopyWebpackPlugin({ patterns: [ + { from: 'src/manifest.json', to: 'manifest.json' }, { from: 'src/popup/popup.html', to: 'popup.html' }, - { from: 'src/popup/recap.html', to: 'recap.html' } + { from: 'src/popup/recap.html', to: 'recap.html' }, + { from: 'src/assets', to: 'assets' } ] }) ],