diff --git a/package-lock.json b/package-lock.json index c546d4fd..de672d26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4567,107 +4567,6 @@ "win32" ] }, - "node_modules/@mantine/core": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.5.tgz", - "integrity": "sha512-PdVNLMgOS2vFhOujRi6/VC9ic8w3UDyKX7ftwDeJ7yQT8CiepUxfbWWYpVpnq23bdWh/7fIT2Pn1EY8r8GOk7g==", - "dependencies": { - "@floating-ui/react": "^0.27.16", - "clsx": "^2.1.1", - "react-number-format": "^5.4.4", - "react-remove-scroll": "^2.7.1", - "react-textarea-autosize": "8.5.9", - "type-fest": "^4.41.0" - }, - "peerDependencies": { - "@mantine/hooks": "8.3.5", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/dates": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.5.tgz", - "integrity": "sha512-LkIdC4eWPNQFv1BU1c52U3Z3RuA3yU1asvTgMEIQ/MdJsGK8GePwpgMH/jKQ8ba/AW9NfksdvtOJ6uIqPwjCkg==", - "dependencies": { - "clsx": "^2.1.1" - }, - "peerDependencies": { - "@mantine/core": "8.3.5", - "@mantine/hooks": "8.3.5", - "dayjs": ">=1.0.0", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/dropzone": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-8.3.5.tgz", - "integrity": "sha512-8eRNoEYQzUOav8Te58osGRt2vbqJO3ZORXgKALY+FhB0YGRCZYmS/gQ2T66SpyrpovY6k6OVi1ScM3wRaJrxUg==", - "dependencies": { - "react-dropzone": "14.3.8" - }, - "peerDependencies": { - "@mantine/core": "8.3.5", - "@mantine/hooks": "8.3.5", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/form": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.5.tgz", - "integrity": "sha512-i9UFiHtO1dlrJXZkquyt+71YcNNxPPSkIcJCRp7k0Tif7bPqWK2xijPDEXzqvA53YvMvEMoqaQCEQLVmH7Esdg==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "klona": "^2.0.6" - }, - "peerDependencies": { - "react": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/hooks": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.5.tgz", - "integrity": "sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==", - "peerDependencies": { - "react": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/modals": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-8.3.5.tgz", - "integrity": "sha512-8pEhVc2NqUcO1+mQab1J5hDwMGKbqwMWMQptF++PUI0e82BGyoxuOdYywWvvW7+UzcA1REMF7uy0mfG9RLcjew==", - "peerDependencies": { - "@mantine/core": "8.3.5", - "@mantine/hooks": "8.3.5", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/notifications": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.3.5.tgz", - "integrity": "sha512-8TvzrPxfdtOLGTalv7Ei1hy2F6KbR3P7/V73yw3AOKhrf1ydS89sqV2ShbsucHGJk9Pto0wjdTPd8Q7pm5MAYw==", - "dependencies": { - "@mantine/store": "8.3.5", - "react-transition-group": "4.4.5" - }, - "peerDependencies": { - "@mantine/core": "8.3.5", - "@mantine/hooks": "8.3.5", - "react": "^18.x || ^19.x", - "react-dom": "^18.x || ^19.x" - } - }, - "node_modules/@mantine/store": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.3.5.tgz", - "integrity": "sha512-qN4fFsDMy86IV9oh1gZlDTv41RAsO0grjx90FGyT5QCv7NTgcavwxB74GBkhp45W8xn+Ms/awKy+6NxnmLmW1w==", - "peerDependencies": { - "react": "^18.x || ^19.x" - } - }, "node_modules/@mdx-js/loader": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-3.1.1.tgz", @@ -26582,6 +26481,7 @@ "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, "engines": { "node": ">=16" }, @@ -27989,7 +27889,7 @@ }, "src/packages/semble-pds-client": { "name": "@cosmik.network/semble-pds-client", - "version": "0.0.4", + "version": "0.0.6", "license": "MIT", "dependencies": { "@atproto/api": "^0.18.0", @@ -28038,13 +27938,14 @@ "name": "semble-webapp", "version": "0.1.0", "dependencies": { - "@mantine/core": "^8.1.3", - "@mantine/dates": "^8.1.3", - "@mantine/dropzone": "^8.1.3", - "@mantine/form": "^8.1.3", - "@mantine/hooks": "^8.1.3", - "@mantine/modals": "^8.1.3", - "@mantine/notifications": "^8.1.3", + "@mantine/code-highlight": "8.3.1", + "@mantine/core": "8.3.1", + "@mantine/dates": "8.3.1", + "@mantine/dropzone": "8.3.1", + "@mantine/form": "8.3.1", + "@mantine/hooks": "8.3.1", + "@mantine/modals": "8.3.1", + "@mantine/notifications": "8.3.1", "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", "@next/mdx": "^15.5.4", @@ -28117,6 +28018,132 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "src/webapp/node_modules/@mantine/code-highlight": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-8.3.1.tgz", + "integrity": "sha512-YRjMuLGnNg8BlzYg1+Dj3ZW3sb4q0P9QBNZwGdKpe4x0dtLOPa3pVPnKWhSiD4/Y0cWUbCiyzUQ+MlzFYnAg9w==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "8.3.1", + "@mantine/hooks": "8.3.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/core": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.1.tgz", + "integrity": "sha512-OYfxn9cTv+K6RZ8+Ozn/HDQXkB8Fmn+KJJt5lxyFDP9F09EHnC59Ldadv1LyUZVBGtNqz4sn6b3vBShbxwAmYw==", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "clsx": "^2.1.1", + "react-number-format": "^5.4.4", + "react-remove-scroll": "^2.7.1", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.41.0" + }, + "peerDependencies": { + "@mantine/hooks": "8.3.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "src/webapp/node_modules/@mantine/dates": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.1.tgz", + "integrity": "sha512-qCGlLnrwu9eQsl+yQC/tEYgTEO8rE6hopagNpTV2/wzLBUywlL/AbtB1yHuOikQgZxXAOLfvIBWNTWUHRtTnfw==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "8.3.1", + "@mantine/hooks": "8.3.1", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/dropzone": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-8.3.1.tgz", + "integrity": "sha512-jsaZhX02Uu8FDWBYNk59wWEBNO0hrayuBfRGqSjY/FQwRHZXIBgfuBsE3AIBJte6ptX+gWSR0Bhwids0LicEtg==", + "dependencies": { + "react-dropzone": "14.3.8" + }, + "peerDependencies": { + "@mantine/core": "8.3.1", + "@mantine/hooks": "8.3.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/form": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.3.1.tgz", + "integrity": "sha512-kmnF5o0Tl/Wi+ZGdqNknoN7QDswxuRo7OlPDRwXuxv/TcazuOIwf7j0p6kFzJ0c/wuqrZfjx3vnOg4Txtmwa1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "klona": "^2.0.6" + }, + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/hooks": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.1.tgz", + "integrity": "sha512-lQutBS+Q0iz/cNFvdrsYassPWo3RtWcmDGJeOtKfHigLzFOhxUuLOkQgepDbMf3WcVMB/tist6Px1PQOv57JTw==", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/modals": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-8.3.1.tgz", + "integrity": "sha512-3+OL1VcrKI91eqfLR4j6gIKHxwCVINNBrBdIVKc4ozAPAF/XI5VXwhXYxV/Nd7B2lxQgsOlIK5rjEKFvTfHZBg==", + "peerDependencies": { + "@mantine/core": "8.3.1", + "@mantine/hooks": "8.3.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/notifications": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.3.1.tgz", + "integrity": "sha512-C1Iqa4g1HNNTLv2/CxOCR1mNlYNFCNtnS0u/JsR+HvtFVrun1namxDG6e6/U0hIva2klogYdivx4cyxmjPFerg==", + "dependencies": { + "@mantine/store": "8.3.1", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "8.3.1", + "@mantine/hooks": "8.3.1", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "src/webapp/node_modules/@mantine/notifications/node_modules/@mantine/store": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.3.1.tgz", + "integrity": "sha512-OZwg0YKbCEKnkFmS9oRLKA8TMriBzO1T6nUib1yfLCx0VFuznllYZiDtaSWNkEYSdnFWCv5hKh5aOD4RHUnQfQ==", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, "src/webapp/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", diff --git a/src/webapp/.storybook/preview.tsx b/src/webapp/.storybook/preview.tsx index 05689d5a..e4710f9e 100644 --- a/src/webapp/.storybook/preview.tsx +++ b/src/webapp/.storybook/preview.tsx @@ -1,4 +1,5 @@ import '@mantine/core/styles.css'; +import '@mantine/code-highlight/styles.css'; import React from 'react'; import { MantineProvider } from '@mantine/core'; diff --git a/src/webapp/app/(dashboard)/settings/advanced/layout.tsx b/src/webapp/app/(dashboard)/settings/advanced/layout.tsx new file mode 100644 index 00000000..f11d9d2d --- /dev/null +++ b/src/webapp/app/(dashboard)/settings/advanced/layout.tsx @@ -0,0 +1,24 @@ +import BackButton from '@/components/navigation/backButton/BackButton'; +import Header from '@/components/navigation/header/Header'; +import type { Metadata } from 'next'; +import { Fragment } from 'react'; + +export const metadata: Metadata = { + title: 'Advanced', + description: 'Advanced', +}; + +interface Props { + children: React.ReactNode; +} + +export default function Layout(props: Props) { + return ( + +
+ Settings +
+ {props.children} +
+ ); +} diff --git a/src/webapp/app/(dashboard)/settings/advanced/loading.tsx b/src/webapp/app/(dashboard)/settings/advanced/loading.tsx new file mode 100644 index 00000000..3d675077 --- /dev/null +++ b/src/webapp/app/(dashboard)/settings/advanced/loading.tsx @@ -0,0 +1,5 @@ +import AdvancedContainerSkeleton from '@/features/settings/containers/advancedContainer/Skeleton.AdvancedContainer'; + +export default function Loading() { + return ; +} diff --git a/src/webapp/app/(dashboard)/settings/advanced/page.tsx b/src/webapp/app/(dashboard)/settings/advanced/page.tsx new file mode 100644 index 00000000..b84546bc --- /dev/null +++ b/src/webapp/app/(dashboard)/settings/advanced/page.tsx @@ -0,0 +1,5 @@ +import AdvancedContainer from '@/features/settings/containers/advancedContainer/AdvancedContainer'; + +export default function Page() { + return ; +} diff --git a/src/webapp/features/cards/components/urlCard/UrlCard.tsx b/src/webapp/features/cards/components/urlCard/UrlCard.tsx index f483c851..b9c395cb 100644 --- a/src/webapp/features/cards/components/urlCard/UrlCard.tsx +++ b/src/webapp/features/cards/components/urlCard/UrlCard.tsx @@ -9,6 +9,8 @@ import { useRouter } from 'next/navigation'; import { isCollectionPage } from '@/lib/utils/link'; import styles from './UrlCard.module.css'; import { CardSize } from '../../types'; +import { useUserSettings } from '@/features/settings/lib/queries/useUserSettings'; +import { CodeHighlight } from '@mantine/code-highlight'; interface Props { size?: CardSize; @@ -26,6 +28,7 @@ interface Props { export default function UrlCard(props: Props) { const router = useRouter(); + const { settings } = useUserSettings(); const handleNavigateToSemblePage = (e: MouseEvent) => { e.stopPropagation(); @@ -50,11 +53,22 @@ export default function UrlCard(props: Props) { onClick={handleNavigateToSemblePage} > - + {settings.tinkerMode ? ( + e.stopPropagation()} + style={{ cursor: 'auto' }} + /> + ) : ( + + )} + + + + + + Tinker mode + + See the Matrix (data exposed as raw JSON) + + + updateSetting('tinkerMode', event.currentTarget.checked) + } + /> + + + + + ); +} diff --git a/src/webapp/features/settings/containers/advancedContainer/Skeleton.AdvancedContainer.tsx b/src/webapp/features/settings/containers/advancedContainer/Skeleton.AdvancedContainer.tsx new file mode 100644 index 00000000..1cf08ce1 --- /dev/null +++ b/src/webapp/features/settings/containers/advancedContainer/Skeleton.AdvancedContainer.tsx @@ -0,0 +1,14 @@ +import { Container, Stack, ButtonGroup } from '@mantine/core'; +import SettingItemSkeleton from '../../components/settingItem/Skeleton.SettingItem'; + +export default function AdvancedContainerSkeleton() { + return ( + + + + + + + + ); +} diff --git a/src/webapp/features/settings/containers/settingsContainer/SettingsContainer.tsx b/src/webapp/features/settings/containers/settingsContainer/SettingsContainer.tsx index f6bf0e2f..ef50c48a 100644 --- a/src/webapp/features/settings/containers/settingsContainer/SettingsContainer.tsx +++ b/src/webapp/features/settings/containers/settingsContainer/SettingsContainer.tsx @@ -7,6 +7,7 @@ import { IoMdInformationCircle, } from 'react-icons/io'; import SettingLogoutItem from '../../components/settingLogoutItem/SettingLogoutItem'; +import { MdScience } from 'react-icons/md'; export default function SettingsContainer() { return ( @@ -18,6 +19,9 @@ export default function SettingsContainer() { Appearance + + Advanced + Help diff --git a/src/webapp/features/settings/lib/queries/useUserSettings.tsx b/src/webapp/features/settings/lib/queries/useUserSettings.tsx new file mode 100644 index 00000000..ed7307ad --- /dev/null +++ b/src/webapp/features/settings/lib/queries/useUserSettings.tsx @@ -0,0 +1,32 @@ +import { useLocalStorage } from '@mantine/hooks'; + +interface UserSettings { + tinkerMode: boolean; +} + +const defaultSettings: UserSettings = { + tinkerMode: false, +}; + +export function useUserSettings() { + const [settings, setSettings] = useLocalStorage({ + key: 'user-settings', + defaultValue: defaultSettings, + }); + + function updateSetting( + key: K, + value: UserSettings[K], + ) { + setSettings((prev) => ({ + ...prev, + [key]: value, + })); + } + + return { + settings, + setSettings, + updateSetting, + }; +} diff --git a/src/webapp/package.json b/src/webapp/package.json index 6df1e4e0..d6f686a6 100644 --- a/src/webapp/package.json +++ b/src/webapp/package.json @@ -26,13 +26,14 @@ "build-storybook": "storybook build" }, "dependencies": { - "@mantine/core": "^8.1.3", - "@mantine/dates": "^8.1.3", - "@mantine/dropzone": "^8.1.3", - "@mantine/form": "^8.1.3", - "@mantine/hooks": "^8.1.3", - "@mantine/modals": "^8.1.3", - "@mantine/notifications": "^8.1.3", + "@mantine/code-highlight": "8.3.1", + "@mantine/core": "8.3.1", + "@mantine/dates": "8.3.1", + "@mantine/dropzone": "8.3.1", + "@mantine/form": "8.3.1", + "@mantine/hooks": "8.3.1", + "@mantine/modals": "8.3.1", + "@mantine/notifications": "8.3.1", "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", "@next/mdx": "^15.5.4", diff --git a/src/webapp/providers/index.tsx b/src/webapp/providers/index.tsx index 63271be2..a46bcf0d 100644 --- a/src/webapp/providers/index.tsx +++ b/src/webapp/providers/index.tsx @@ -4,6 +4,7 @@ import { AuthProvider } from '@/hooks/useAuth'; import MantineProvider from './mantine'; import TanStackQueryProvider from './tanstack'; import { NavbarProvider } from './navbar'; +import { SettingsProvider } from './settings'; interface Props { children: React.ReactNode; @@ -14,7 +15,9 @@ export default function Providers(props: Props) { - {props.children} + + {props.children} + diff --git a/src/webapp/providers/mantine.tsx b/src/webapp/providers/mantine.tsx index 92185508..533f620b 100644 --- a/src/webapp/providers/mantine.tsx +++ b/src/webapp/providers/mantine.tsx @@ -1,20 +1,41 @@ 'use client'; +import '@mantine/core/styles.css'; +import '@mantine/notifications/styles.css'; +import '@mantine/code-highlight/styles.css'; import { theme } from '@/styles/theme'; import { MantineProvider as BaseProvider } from '@mantine/core'; -import '@mantine/core/styles.css'; +import { + CodeHighlightAdapterProvider, + createShikiAdapter, +} from '@mantine/code-highlight'; + import { Notifications } from '@mantine/notifications'; -import '@mantine/notifications/styles.css'; interface Props { children: React.ReactNode; } +// Shiki requires async code to load the highlighter +async function loadShiki() { + const { createHighlighter } = await import('shiki'); + const shiki = await createHighlighter({ + langs: ['html', 'css', 'js', 'ts', 'tsx', 'json'], + themes: [], + }); + + return shiki; +} + +const shikiAdapter = createShikiAdapter(loadShiki); + export default function MantineProvider(props: Props) { return ( - - {props.children} + + + {props.children} + ); } diff --git a/src/webapp/providers/navbar.tsx b/src/webapp/providers/navbar.tsx index c4d432ea..1a4182bf 100644 --- a/src/webapp/providers/navbar.tsx +++ b/src/webapp/providers/navbar.tsx @@ -17,11 +17,11 @@ const NavbarContext = createContext({ toggleDesktop: () => {}, }); -interface ProviderProps { +interface Props { children: React.ReactNode; } -export function NavbarProvider({ children }: ProviderProps) { +export function NavbarProvider(props: Props) { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(false); const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true); @@ -29,7 +29,7 @@ export function NavbarProvider({ children }: ProviderProps) { - {children} + {props.children} ); } diff --git a/src/webapp/providers/settings.tsx b/src/webapp/providers/settings.tsx new file mode 100644 index 00000000..0c420cea --- /dev/null +++ b/src/webapp/providers/settings.tsx @@ -0,0 +1,25 @@ +import { useUserSettings } from '@/features/settings/lib/queries/useUserSettings'; +import { createContext, useContext } from 'react'; + +const SettingsContext = createContext | null>(null); + +interface Props { + children: React.ReactNode; +} + +export function SettingsProvider(props: Props) { + const settings = useUserSettings(); + return ( + + {props.children} + + ); +} + +export function useSettings() { + const ctx = useContext(SettingsContext); + if (!ctx) throw new Error('useSettings must be used inside SettingsProvider'); + return ctx; +}