diff --git a/package.json b/package.json index 82bb471b..f5bacf4b 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "to-string-loader": "^1.2.0", "ts-loader": "^9.4.2", "ts-node": "^10.9.1", - "typescript": "^5.0.4", + "typescript": "^5.3.2", "url": "^0.11.0", "walk-sync": "^3.0.0", "webextension-polyfill": "^0.10.0", diff --git a/src/pages/newtab/start.tsx b/src/pages/newtab/start.tsx index f9134889..26f88c1b 100644 --- a/src/pages/newtab/start.tsx +++ b/src/pages/newtab/start.tsx @@ -7,7 +7,7 @@ import { Suspense, lazy, useEffect, useMemo, useState } from 'react'; import { Modal } from '@components/Modal'; import { AnimatePresence, LazyMotion, MotionConfig, m } from 'framer-motion'; import { DirectionProvider } from '@radix-ui/react-direction'; -import { useFolders } from '@utils/user-data/hooks'; +import { getFolderDetails, setFolderDetails, useFolders } from '@utils/user-data/hooks'; import { FolderContent } from './components/FolderContent'; import { useHotkeys, useMirrorStateToRef, usePrevious } from '@utils/hooks'; import { storage, useBrowserStorageValue } from '@utils/storage/api'; @@ -22,7 +22,8 @@ import { useTranslation } from 'react-i18next'; import { IS_ANDROID, IS_IPAD, IS_TOUCH_DEVICE } from '@utils/device'; import { WidgetWindowsProvider, useWidgetWindows } from '@components/WidgetExpandArea'; import { loadAndMigrateStorage } from '@utils/storage/migrations'; -import { Folder } from '@utils/user-data/types'; +import { Folder, homeFolder } from '@utils/user-data/types'; +import { findOverlapItems, findPositionForItemInGrid } from '@utils/grid'; const SettingsModal = lazy(() => import('./settings/Settings').then(m => ({ 'default': m.SettingsModal }))); const WhatsNew = lazy(() => import('@components/WhatsNew').then(m => ({ 'default': m.WhatsNew }))); @@ -128,7 +129,7 @@ const Start = () => { useHotkeys('alt+8', () => switchToFolderByIndex(7)); useHotkeys('alt+9', () => switchToFolderByIndex(8)); - console.log('Current language', {language, dir}); + console.log('Current language', { language, dir }); return ( @@ -245,6 +246,46 @@ getAllCustomIcons(); loadAndMigrateStorage() .then(() => initTranslation()) + .then(async (): Promise => { + let folders = await storage.getOne('folders'); + if (!folders) folders = []; + folders.unshift(homeFolder); + console.log('Checking for overlay'); + for (const folder of folders) { + const { widgets } = await getFolderDetails(folder.id); + const reversedWidgets = [...widgets].reverse(); + const overlapItems = findOverlapItems(reversedWidgets); + if (overlapItems.length) { + console.log('Found ovelap:', overlapItems); + const overlapItemIds = overlapItems.map(i => i.instanceId); + const layoutWithoutOverlay = widgets.filter(w => { + return !overlapItemIds.includes(w.instanceId); + }); + overlapItems.map(item => { + const columns = Math.max(...layoutWithoutOverlay.map(i => i.x + i.width), 0); + const rows = Math.max(...layoutWithoutOverlay.map(i => i.y + i.height), 0); + let position = findPositionForItemInGrid({ + grid: {rows, columns}, + layout: layoutWithoutOverlay, + item, + }); + if (!position) { + position = { + x: columns, + y: 0, + } + } + layoutWithoutOverlay.push({ + ...item, + ...position, + }); + }); + setFolderDetails(folder.id, { + widgets: layoutWithoutOverlay, + }); + } + } + }) .then(() => { mountPage( {/* strict mode temporary disabled due to strict https://github.com/framer/motion/issues/2094 */} diff --git a/src/utils/grid.ts b/src/utils/grid.ts index 96284044..2c655dba 100644 --- a/src/utils/grid.ts +++ b/src/utils/grid.ts @@ -29,6 +29,13 @@ export type Layout = LayoutItem[]; type Grid2DArray = boolean[][]; +export class OverlapError extends Error { + constructor() { + super('Elements in the layout have overlap'); + this.name = "ValidationError"; + } +} + export const calculateColumnWidth = (containerWidth: number, desiredSize: number, minBoxSize: number) => { const columns = Math.round(containerWidth / desiredSize); const colWidth = Math.min(Math.max(Math.floor(containerWidth / columns), minBoxSize), desiredSize); @@ -132,7 +139,7 @@ const layoutItemToSectors = (item: LayoutItem) => { return arr; } -export const layoutTo2DArray = ({ grid, layout, allowOverlay = false }: { grid: GridDimensions, layout: Layout, allowOverlay?: boolean }): Grid2DArray => { +export const layoutTo2DArray = ({ grid, layout, allowOverlay = false }: { grid: Pick, layout: Layout, allowOverlay?: boolean }): Grid2DArray => { const arr = [...Array(grid.rows)].map(() => { return [...Array(grid.columns)].map(() => false); }); @@ -143,7 +150,7 @@ export const layoutTo2DArray = ({ grid, layout, allowOverlay = false }: { grid: if (s.x >= grid.columns || s.y >= grid.rows) return; if (arr[s.y][s.x] && !allowOverlay) { console.log('Item:', item); - throw new Error('elements in layout have overlay'); + throw new OverlapError(); } arr[s.y][s.x] = true; }) @@ -152,6 +159,23 @@ export const layoutTo2DArray = ({ grid, layout, allowOverlay = false }: { grid: return arr; }; +export const findOverlapItems = (layout: Layout): LayoutItem[] => { + const overlapItems: LayoutItem[] = []; + const arr: boolean[][] = []; + for (const item of layout) { + const itemSectors = layoutItemToSectors(item); + for (const s of itemSectors) { + if (!arr[s.y]) arr[s.y] = []; + if (arr[s.y][s.x]) { + overlapItems.push(item); + break; + } + arr[s.y][s.x] = true; + } + } + return overlapItems; +}; + export const willItemOverlay = ({ arr, item }: { arr: Grid2DArray, item: LayoutItem }): boolean => { const itemSectors = layoutItemToSectors(item); for (const sector of itemSectors) { @@ -171,7 +195,7 @@ export const canPlaceItemInGrid = ({ grid, layout, item, position }: { grid: Gri return true; }; -export const findPositionForItemInGrid = ({ grid, layout, item }: { grid: GridDimensions, layout: Layout, item: LayoutItemSize }): false | Position => { +export const findPositionForItemInGrid = ({ grid, layout, item }: { grid: Pick, layout: Layout, item: LayoutItemSize }): false | Position => { const arr = layoutTo2DArray({ grid, layout }); for (let i = 0; i < arr.length; i++) { diff --git a/tsconfig.json b/tsconfig.json index afc4becf..b02b70b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "include": ["declarations.d.ts", "src/**/*", "webpack.config.ts"], "exclude": ["node_modules", "dist", "safari-app"], "compilerOptions": { - "lib": ["es6", "es2019", "dom", "dom.iterable", "WebWorker"], + "lib": ["es6", "es2023", "dom", "dom.iterable", "WebWorker"], "baseUrl": ".", "paths": { "@utils/*": ["./src/utils/*"], diff --git a/webpack.config.ts b/webpack.config.ts index e02e3b3f..8a2e9a97 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -87,10 +87,7 @@ const generateManifest = ( ], chrome_url_overrides: { - newtab: "pages/newtab/start.html" - }, - chrome_settings_overrides: { - homepage: "pages/newtab/start.html" + newtab: "/pages/newtab/start.html" }, web_accessible_resources: [ { @@ -188,6 +185,11 @@ const generateManifest = ( if (targetBrowser === 'firefox') { manifest.optional_permissions!.push('webRequest', 'webRequestBlocking'); + + // Despite the name this seem to work only in Firefox (for Chrome new tab page and home page are the same) + manifest.chrome_settings_overrides = { + homepage: "pages/newtab/start.html" + }; } return manifest; diff --git a/yarn.lock b/yarn.lock index e62c04ae..78a18b12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1409,14 +1409,7 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-direction@1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz" - integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== - dependencies: - "@babel/runtime" "^7.13.10" - -"@radix-ui/react-direction@^1.0.1": +"@radix-ui/react-direction@1.0.1", "@radix-ui/react-direction@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== @@ -6511,10 +6504,10 @@ typed-function@^4.1.0: resolved "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz" integrity sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg== -typescript@^5.0.4: - version "5.0.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz" - integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== +typescript@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" + integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== unbox-primitive@^1.0.2: version "1.0.2"