+ htmlLines.push(
+ `
` /* <- TODO: Do not hardcode sans-serif */ /* <- [🎗] */,
+ );
+
+ isFontTagOpened = true;
+ }
+ }
+ if (isFontTagOpened) {
+ htmlLines.push('
');
+ }
+
+ html = htmlLines.join('\n');
+
+ html = prettifyHtml(html);
+ html = spaceTrim(html);
+
+ return html;
+}
+
+/**
+ * TODO: [🧠] Do here somewhere normalizeDashes
+ * TODO: [🧠] Do here somewhere linkMarkdown (but with dynamic list of what to link)
+ */
diff --git a/src/components/Content/Maxdown/samples/10-simple.md b/src/components/Content/Maxdown/samples/10-simple.md
new file mode 100644
index 0000000000..905cb8b2cb
--- /dev/null
+++ b/src/components/Content/Maxdown/samples/10-simple.md
@@ -0,0 +1,3 @@
+# Simple
+
+Test of simple markdown.
diff --git a/src/components/Content/Maxdown/samples/20-wallpaper.md b/src/components/Content/Maxdown/samples/20-wallpaper.md
new file mode 100644
index 0000000000..729429c29c
--- /dev/null
+++ b/src/components/Content/Maxdown/samples/20-wallpaper.md
@@ -0,0 +1,21 @@
+
+
+# Komunikace v proměnách
+
+> Komunikace – spojení, které nás mění a proměňuje
+
+---
+
+> Vývoj komunikace se neustále vyvíjí a ovlivňuje naše životy více, než si uvědomujeme. Moderní technologie nám umožňuje být ve spojení s ostatními neustále, ale zároveň nám může bránit v opravdovém porozumění a spolupráci.
+
+Komunikace je klíčovým prvkem našeho každodenního života a její význam v současném světě je nezastupitelný. Od pradávna se lidé snažili najít způsoby, jak si navzájem sdělovat své myšlenky, pocity a potřeby. Původní formy komunikace, jako například hlasová řeč a posunky, se postupem času vyvinuly do komplexnějších forem, jako jsou písmo, tisk, telefon nebo internet.
+
+Vývoj komunikace v průběhu času byl nutný pro zajištění stále se rozrůstajícího společenství a různorodých způsobů využití této schopnosti. Komunikace nám umožňuje sdílet informace, učit se od sebe, navazovat vztahy a spolupracovat. Díky tomu se náš svět stává stále více propojeným a globálním.
+
+Nicméně, moderní technologie, které jsou neodmyslitelnou součástí našeho každodenního života, vedou k mnoha proměnám a úskalím v oblasti komunikace. Zatímco jsou nám nástroji jako sociální sítě nebo mobilní telefony k dispozici pro okamžité připojení ke světu, častokrát nás odvádějí od skutečných interakcí s ostatními lidmi. Komunikace přes média neustále přináší nové výzvy, jako jsou manipulace nebo falšování informací, což může vést k nedorozuměním a konfliktům.
+
+Je ale důležité uvědomit si, že komunikace není pouze o sdílení informací, ale také o porozumění a spolupráci. Moderní technologie nám mohou tento proces umožňovat, pokud se dokážeme naučit je správně využívat. Jedna z možností je využití online nástrojů pro učení se jazyků a komunikaci s cizinci. Další možností je využití virtuálních schůzek pro zlepšení pracovního procesu a spolupráce týmu.
+
+V závěru našeho eseje se nabízí otázka, jak můžeme využít komunikaci pro lepší porozumění a spolupráci mezi námi. Prvním krokem je uvědomit si, že komunikace není pouze o elektronických médiích, ale také o osobní interakci. Dále můžeme využít různé nástroje pro zlepšení našich komunikačních dovedností, jako jsou kurzy a tréninky. A nakonec, najít rovnováhu mezi využíváním moderních technologií a skutečnými osobními interakcemi.
+
+Vývoj komunikace byl a stále bude významným tématem, který se týká každého z nás a ovlivňuje naše životy ve všech aspektech. Je důležité si uvědomit její význam v současném světě, ale také být kritičtí vůči jejím proměnám a aktivně se podílet na vytváření lepší komunikace mezi námi.
diff --git a/src/components/Content/Maxdown/samples/30-multiple-fonts.md b/src/components/Content/Maxdown/samples/30-multiple-fonts.md
new file mode 100644
index 0000000000..002156ad0f
--- /dev/null
+++ b/src/components/Content/Maxdown/samples/30-multiple-fonts.md
@@ -0,0 +1,33 @@
+
+
+# Komunikace v proměnách
+
+> Komunikace – spojení, které nás mění a proměňuje
+
+
+
+> Vývoj komunikace se neustále vyvíjí a ovlivňuje naše životy více, než si uvědomujeme. Moderní technologie nám umožňuje být ve spojení s ostatními neustále, ale zároveň nám může bránit v opravdovém porozumění a spolupráci.
+
+
+
+Komunikace je klíčovým prvkem našeho každodenního života a její význam v současném světě je nezastupitelný. Od pradávna se lidé snažili najít způsoby, jak si navzájem sdělovat své myšlenky, pocity a potřeby. Původní formy komunikace, jako například hlasová řeč a posunky, se postupem času vyvinuly do komplexnějších forem, jako jsou písmo, tisk, telefon nebo internet.
+
+
+
+Vývoj komunikace v průběhu času byl nutný pro zajištění stále se rozrůstajícího společenství a různorodých způsobů využití této schopnosti. Komunikace nám umožňuje sdílet informace, učit se od sebe, navazovat vztahy a spolupracovat. Díky tomu se náš svět stává stále více propojeným a globálním.
+
+
+
+Nicméně, moderní technologie, které jsou neodmyslitelnou součástí našeho každodenního života, vedou k mnoha proměnám a úskalím v oblasti komunikace. Zatímco jsou nám nástroji jako sociální sítě nebo mobilní telefony k dispozici pro okamžité připojení ke světu, častokrát nás odvádějí od skutečných interakcí s ostatními lidmi. Komunikace přes média neustále přináší nové výzvy, jako jsou manipulace nebo falšování informací, což může vést k nedorozuměním a konfliktům.
+
+
+
+Je ale důležité uvědomit si, že komunikace není pouze o sdílení informací, ale také o porozumění a spolupráci. Moderní technologie nám mohou tento proces umožňovat, pokud se dokážeme naučit je správně využívat. Jedna z možností je využití online nástrojů pro učení se jazyků a komunikaci s cizinci. Další možností je využití virtuálních schůzek pro zlepšení pracovního procesu a spolupráce týmu.
+
+
+
+V závěru našeho eseje se nabízí otázka, jak můžeme využít komunikaci pro lepší porozumění a spolupráci mezi námi. Prvním krokem je uvědomit si, že komunikace není pouze o elektronických médiích, ale také o osobní interakci. Dále můžeme využít různé nástroje pro zlepšení našich komunikačních dovedností, jako jsou kurzy a tréninky. A nakonec,
+
+
+
+Vývoj komunikace byl a stále bude významným tématem, který se týká každého z nás a ovlivňuje naše životy ve všech aspektech. Je důležité si uvědomit její význam v současném světě, ale také být kritičtí vůči jejím proměnám a aktivně se podílet na vytváření lepší komunikace mezi námi.
diff --git a/src/components/Content/Maxdown/samples/50-custom-components.md b/src/components/Content/Maxdown/samples/50-custom-components.md
new file mode 100644
index 0000000000..28280b2314
--- /dev/null
+++ b/src/components/Content/Maxdown/samples/50-custom-components.md
@@ -0,0 +1,29 @@
+# Components
+
+## Button
+
+
Buy
+
+
+## Code
+
+
+
+ Feature 1
+ Feature 2
+ Feature 3
+ Feature 4
+
+
+ Feature 1
+ Feature 2
+ Feature 3
+ Feature 4
+
+
+ Feature 1
+ Feature 2
+ Feature 3
+ Feature 4
+
+
diff --git a/src/components/Content/Maxdown/samples/50-html-components.md.todo b/src/components/Content/Maxdown/samples/50-html-components.md.todo
new file mode 100644
index 0000000000..a586856eb1
--- /dev/null
+++ b/src/components/Content/Maxdown/samples/50-html-components.md.todo
@@ -0,0 +1,27 @@
+
+
+# Components
+
+## Button
+
+
Buy
+
+## Code
+
+
+
+```javascript
+const a = 1;
+```
+
+## Image
+
+
+
+## Iframe
+
+
diff --git a/src/components/Content/Maxdown/validateMaxdown.ts b/src/components/Content/Maxdown/validateMaxdown.ts
new file mode 100644
index 0000000000..3135b7c1fe
--- /dev/null
+++ b/src/components/Content/Maxdown/validateMaxdown.ts
@@ -0,0 +1,20 @@
+import { string_maxdown } from '../../../utils/typeAliases';
+
+/**
+ * Validate maxdown content
+ *
+ * If content is not a valid maxdown, throws an error
+ * If content is a valid maxdown,
+ */
+export function validateMaxdown(content: unknown): string_maxdown {
+ if (typeof content !== 'string') {
+ throw new Error('Content is not a string');
+ }
+
+ return content as string_maxdown;
+}
+
+/**
+ * TODO: !! Do here a real validation / sanitization
+ * TODO: [🧠][🚓] Is/which combination it better to use asserts/check, validate or is utility function?
+ */
diff --git a/src/components/MarkdownContent/mapLinksInHtml.test.ts b/src/components/Content/mapLinksInHtml.test.ts
similarity index 100%
rename from src/components/MarkdownContent/mapLinksInHtml.test.ts
rename to src/components/Content/mapLinksInHtml.test.ts
diff --git a/src/components/MarkdownContent/mapLinksInHtml.ts b/src/components/Content/mapLinksInHtml.ts
similarity index 100%
rename from src/components/MarkdownContent/mapLinksInHtml.ts
rename to src/components/Content/mapLinksInHtml.ts
diff --git a/src/components/MarkdownContent/markdownConverter.test.ts b/src/components/Content/markdownConverter.test.ts
similarity index 100%
rename from src/components/MarkdownContent/markdownConverter.test.ts
rename to src/components/Content/markdownConverter.test.ts
diff --git a/src/components/MarkdownContent/markdownConverter.ts b/src/components/Content/markdownConverter.ts
similarity index 100%
rename from src/components/MarkdownContent/markdownConverter.ts
rename to src/components/Content/markdownConverter.ts
diff --git a/src/components/MarkdownContent/useHash.ts b/src/components/Content/useHash.ts
similarity index 100%
rename from src/components/MarkdownContent/useHash.ts
rename to src/components/Content/useHash.ts
diff --git a/src/components/ControlPanel/ControlPanel.tsx b/src/components/ControlPanel/ControlPanel.tsx
index 8c77100509..569380894e 100644
--- a/src/components/ControlPanel/ControlPanel.tsx
+++ b/src/components/ControlPanel/ControlPanel.tsx
@@ -25,7 +25,7 @@ export function ControlPanel() {
return (
{/*
{wallpaperId}
*/}
@@ -97,7 +97,7 @@ export function ControlPanel() {
)}
{/*
- Note: In the
+ TODO: [🧠] What is the best way how to navigate home?
-
{/*
*/}
- {' '}
+
);
}
diff --git a/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts b/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts
index b813e716cc..57d4e68c1a 100644
--- a/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts
+++ b/src/components/ControlPanel/RandomWallpaper/RandomWallpaperManager.ts
@@ -5,9 +5,12 @@ import type { RecommendWallpaperResponse } from '../../../pages/api/recommend-wa
import { IWallpaperSerialized } from '../../../utils/IWallpaper';
import { randomItem } from '../../../utils/randomItem';
import { provideClientIdWithoutVerification } from '../../../utils/supabase/provideClientIdWithoutVerification';
-import { string_wallpaper_id } from '../../../utils/typeAliases';
+import { string_color } from '../../../utils/typeAliases';
export type IWallpaperInStorage = Pick
;
+export type IWallpaperInMockedApi = Pick & {
+ primaryColor: string_color;
+};
/**
* RandomWallpaperManager is a class that manages the random wallpapers which will be shown next.
@@ -144,26 +147,24 @@ export class RandomWallpaperManager {
return /* not await */ this.prefetchIfNeeded();
}
- private welcomeWallpapers: null | Array<{
- id: string_wallpaper_id;
- }> = null;
+ private welcomeWallpapers: null | Array = null;
- public async getWelcomeWallpaper(): Promise> {
+ public async getWelcomeWallpapers(): Promise> {
if (this.welcomeWallpapers === null) {
const response = await fetch(`${NEXT_PUBLIC_URL.href}mocked-api/wallpapers-min-loved.json`);
const { wallpapers } = (await response.json()) as {
- wallpapers: Array<{
- id: string_wallpaper_id;
- // [2]
- // primaryColor: string_color;
- // likedStatus: keyof typeof LikedStatus;
- }>;
+ wallpapers: Array;
};
this.welcomeWallpapers = wallpapers;
}
- // TODO: Do here a preloading when [2] there will be src in wallpapers-min-loved.json
+ // TODO: Do here a preloading when there will be src in wallpapers-min-loved.json
// > await this.preloadRandomWallpaper(randomWallpaper);
- return randomItem(...this.welcomeWallpapers);
+ return this.welcomeWallpapers;
+ }
+
+ public async getWelcomeWallpaper(): Promise {
+ const welcomeWallpapers = await this.getWelcomeWallpapers();
+ return randomItem(...welcomeWallpapers);
}
public async getRandomWallpaper(): Promise {
diff --git a/src/components/CopilotInput/CopilotInput.tsx b/src/components/CopilotInput/CopilotInput.tsx
index 252839c565..6021ab8660 100644
--- a/src/components/CopilotInput/CopilotInput.tsx
+++ b/src/components/CopilotInput/CopilotInput.tsx
@@ -1,18 +1,19 @@
+import type { string_prompt } from '@promptbook/types';
import Image from 'next/image';
import { useCallback, useRef, useState } from 'react';
import spaceTrim from 'spacetrim';
import { classNames } from '../../utils/classNames';
import { focusRef } from '../../utils/focusRef';
import { useRotatingPlaceholder } from '../../utils/hooks/useRotatingPlaceholder';
-import { string_prompt } from '../../utils/typeAliases';
-import { TorusInteractiveImage } from '../TaskInProgress/TorusInteractiveImage';
+import { message } from '../../utils/typeAliases';
+import { LoadingInteractiveImage } from '../TaskInProgress/LoadingInteractiveImage';
import styles from './CopilotInput.module.css';
interface useRouterProps {
/**
* Label for the input
*/
- label: string;
+ label: message;
/**
* The html comment to export
@@ -42,9 +43,8 @@ export function CopilotInput(props: useRouterProps) {
let prompt = inputRef.current?.value || '';
+ setRunning(true);
try {
- setRunning(true);
-
// TODO: [🍛] Make same normalization as in the backend
prompt = spaceTrim(prompt);
@@ -66,7 +66,7 @@ export function CopilotInput(props: useRouterProps) {
}, [inputRef, onPrompt, isRunning]);
return (
-
+
diff --git a/src/components/CopilotPanel/CopilotPanel.tsx b/src/components/CopilotPanel/CopilotPanel.tsx
index d916f9814a..e6b25bda0e 100644
--- a/src/components/CopilotPanel/CopilotPanel.tsx
+++ b/src/components/CopilotPanel/CopilotPanel.tsx
@@ -1,38 +1,44 @@
+import type { string_prompt } from '@promptbook/types';
import Image from 'next/image';
+import Link from 'next/link';
import { useRouter } from 'next/router';
import { useCallback, useMemo, useRef, useState } from 'react';
import spaceTrim from 'spacetrim';
import { COPILOT_PLACEHOLDERS, FONTS, IS_VERIFIED_EMAIL_REQUIRED } from '../../../config';
-import type {
- UpdateWallpaperContentRequest,
- UpdateWallpaperContentResponse,
-} from '../../pages/api/update-wallpaper-content';
+import { getExecutionTools } from '../../ai/prompt-templates/getExecutionTools';
+import { webgptPtpLibrary } from '../../ai/prompt-templates/webgptPtpLibrary';
import { classNames } from '../../utils/classNames';
import { computeWallpaperUriid } from '../../utils/computeWallpaperUriid';
+import { removeContentComments } from '../../utils/content/removeContentComments';
import { focusRef } from '../../utils/focusRef';
import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper';
import type { LikedStatus } from '../../utils/hooks/useLikedStatusOfCurrentWallpaper';
+import { useLocale } from '../../utils/hooks/useLocale';
import { useRotatingPlaceholder } from '../../utils/hooks/useRotatingPlaceholder';
import { serializeWallpaper } from '../../utils/hydrateWallpaper';
import { randomItem } from '../../utils/randomItem';
import { shuffleItems } from '../../utils/shuffleItems';
import { getSupabaseForBrowser } from '../../utils/supabase/getSupabaseForBrowser';
import { provideClientId } from '../../utils/supabase/provideClientId';
-import { string_prompt } from '../../utils/typeAliases';
+import { validateMaxdown } from '../Content/Maxdown/validateMaxdown';
import { parseKeywordsFromWallpaper } from '../Gallery/GalleryFilter/utils/parseKeywordsFromWallpaper';
import { Hint } from '../Hint/Hint';
+import { addFontToContent } from '../ImportFonts/addFontToContent';
import { changeFontsInContent } from '../ImportFonts/changeFontInContent';
-import { ImportFonts } from '../ImportFonts/ImportFonts';
+import { extractFontsFromContent } from '../ImportFonts/extractFontsFromContent';
import { PublishLink } from '../PublishModal/PublishLink';
-import { TorusInteractiveImage } from '../TaskInProgress/TorusInteractiveImage';
+import { LoadingInteractiveImage } from '../TaskInProgress/LoadingInteractiveImage';
import { WallpaperLink } from '../WallpaperLink/WallpaperLink';
import styles from './CopilotPanel.module.css';
+import { CopilotPanelChangeFont } from './CopilotPanelChangeFont';
+import { CopilotPanelRotateColors } from './CopilotPanelRotateColors';
/**
* Renders the co-pilot panel for text commands to edit the page.
*/
export function CopilotPanel() {
const router = useRouter();
+ const locale = useLocale();
const [wallpaper, modifyWallpaper] = useCurrentWallpaper();
const [runningPrompt, setRunningPrompt] = useState
(null);
const [isMenuOpen, setMenuOpen] = useState(false); /* <- TODO: useToggle */
@@ -40,14 +46,14 @@ export function CopilotPanel() {
const placeholders = useMemo(() => shuffleItems(...COPILOT_PLACEHOLDERS), []);
const placeholder = useRotatingPlaceholder(...placeholders);
const randomFont = useMemo(
- () => randomItem(...FONTS) /* <- TODO: [🧠] Some better heurictic than pure random */,
+ () => randomItem(...FONTS) /* <- TODO: [🧠][🔠] Some better heurictic than pure random */,
// Note: Wallpaper is dependency because we want to offer new font after each change of the font
/* eslint-disable-next-line react-hooks/exhaustive-deps */
[wallpaper],
);
const modifyWallpaperFont = useCallback(() => {
modifyWallpaper((modifiedWallpaper) => {
- modifiedWallpaper.content = changeFontsInContent(modifiedWallpaper.content, randomFont);
+ modifiedWallpaper.content = changeFontsInContent(modifiedWallpaper.content, randomFont.fontFamily);
modifiedWallpaper.saveStage = 'EDITED';
return modifiedWallpaper;
});
@@ -83,37 +89,71 @@ export function CopilotPanel() {
`),
);
- const { content: oldContent } = wallpaper;
+ let { content: oldContent } = wallpaper;
- const response = await fetch(
- `/api/update-wallpaper-content?clientId=${await provideClientId({
- isVerifiedEmailRequired: IS_VERIFIED_EMAIL_REQUIRED.EDIT,
- })}`,
+ const fonts = extractFontsFromContent(oldContent);
+ if (fonts.size !== 1) {
+ throw new Error(`Expected exactly one font in the content, got ${fonts.size}`);
+ }
+ const font = Array.from(fonts)[0]!;
+
+ oldContent = removeContentComments(oldContent);
+
+ const updateWebsiteContentLocaleMap = {
+ en: 'updateWebsiteContent',
+ cs: 'updateWebsiteContentCs',
+ /* <- TODO: [👧] Constrain key to only existing PTPs in the library */
+ };
+
+ const { newContent } = await webgptPtpLibrary.createExecutor(
+ updateWebsiteContentLocaleMap[
+ locale
+ ] /* <- TODO: !! Deal here with locale better - detect from content NOT app */,
+ getExecutionTools(
+ await provideClientId({
+ isVerifiedEmailRequired: IS_VERIFIED_EMAIL_REQUIRED.EDIT,
+ }),
+ ),
+ )(
{
- method: 'POST',
- body: JSON.stringify({
- prompt,
- wallpaper: { content: oldContent },
- } satisfies UpdateWallpaperContentRequest),
+ oldContent,
+ rawAssignment: prompt,
+ },
+ (taskProgress) => {
+ console.info('CopilotPanel: Update wallpaper content: ', { taskProgress });
},
);
- if (response.ok === false) {
- // TODO: [🈵] If 4XX error, show also the message from json body
- throw new Error(`Prompt failed with status ${response.status}`);
- }
+ const newContentWithFont = addFontToContent(validateMaxdown(newContent || ''), font);
+
+ /*/
+ const newContentWithMetadata = spaceTrim(
+ (block) => `
+ ${block(newContentWithFont)}
+
+ ---
+
+
+
+ ## Old content:
+
+ ${block(oldContent)}
- const {
- updatedWallpaper: { content: newContent },
- } = (await response.json()) as UpdateWallpaperContentResponse;
+
+ `, // <- TODO: !! Just newContent, maybe use this in some debug mode
+ );
+ /**/
+ /**/
+ const newContentWithMetadata = newContentWithFont;
+ /**/
- console.info({ oldContent, newContent });
+ console.info('CopilotPanel: Update wallpaper content: ', { oldContent, newContent });
const newWallpaper = modifyWallpaper((modifiedWallpaper) => {
// Note: [🗄] title is computed after each change id+parent+author+keywords are computed just once before save
// TODO: Use here addWallpaperComputables
modifiedWallpaper.parent = modifiedWallpaper.id;
- modifiedWallpaper.content = newContent + '\n\n
' + prompt;
+ modifiedWallpaper.content = newContentWithMetadata;
modifiedWallpaper.saveStage = 'SAVING';
modifiedWallpaper.keywords = Array.from(parseKeywordsFromWallpaper(modifiedWallpaper));
modifiedWallpaper.id = computeWallpaperUriid(modifiedWallpaper);
@@ -133,10 +173,10 @@ export function CopilotPanel() {
} finally {
setRunningPrompt(null);
}
- }, [router, wallpaper, modifyWallpaper, runningPrompt, inputRef]);
+ }, [locale, router, wallpaper, modifyWallpaper, runningPrompt, inputRef]);
return (
-
+
) : (
-
+
)}
@@ -279,18 +319,16 @@ export function CopilotPanel() {
Edit markdown
- {/* <- TODO: Should be here "Edit markdown" or "Edit content" */}
+ {/* <- TODO: [🧠] Should be here "Edit markdown" or "Edit content" or "Advanced edit"
+ + It should be in the submenu of "Advanced edits"
+ */}
- */
- />
-
+
+
+
+
+
+ Make new web
+
Contact
+
{/*
TODO: !! The menu should be like this:
- [x] Show as visitor
- [~] Share
- [x] Get the web
- - [ ] Edit
+ - [x] Edit
- [ ] - advanced prompting
- [ ] - colors
- [ ] - content
diff --git a/src/components/CopilotPanel/CopilotPanelChangeFont.tsx b/src/components/CopilotPanel/CopilotPanelChangeFont.tsx
new file mode 100644
index 0000000000..4143574d7f
--- /dev/null
+++ b/src/components/CopilotPanel/CopilotPanelChangeFont.tsx
@@ -0,0 +1,39 @@
+import { useCallback, useMemo } from 'react';
+import { FONTS } from '../../../config';
+import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper';
+import { randomItem } from '../../utils/randomItem';
+import { changeFontsInContent } from '../ImportFonts/changeFontInContent';
+import { ImportFonts } from '../ImportFonts/ImportFonts';
+
+/**
+ * Renders the co-pilots panel for changing the font of the page.
+ */
+export function CopilotPanelChangeFont() {
+ const [wallpaper, modifyWallpaper] = useCurrentWallpaper();
+ const randomFont = useMemo(
+ () => randomItem(...FONTS) /* <- TODO: [🧠][🔠] Some better heurictic than pure random */,
+ // Note: Wallpaper is dependency because we want to offer new font after each change of the font
+ /* eslint-disable-next-line react-hooks/exhaustive-deps */
+ [wallpaper],
+ );
+ const modifyWallpaperFont = useCallback(() => {
+ modifyWallpaper((modifiedWallpaper) => {
+ modifiedWallpaper.content = changeFontsInContent(modifiedWallpaper.content, randomFont.fontFamily);
+ modifiedWallpaper.saveStage = 'EDITED';
+ return modifiedWallpaper;
+ });
+ }, [modifyWallpaper, randomFont]);
+
+ return (
+ <>
+
*/
+ />
+
+ >
+ );
+}
diff --git a/src/components/CopilotPanel/CopilotPanelRotateColors.tsx b/src/components/CopilotPanel/CopilotPanelRotateColors.tsx
new file mode 100644
index 0000000000..241f9a3104
--- /dev/null
+++ b/src/components/CopilotPanel/CopilotPanelRotateColors.tsx
@@ -0,0 +1,37 @@
+import { useCallback } from 'react';
+import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper';
+import { rotateItems } from '../../utils/rotateItems';
+
+/**
+ * Renders the co-pilots panel for rotating the colors of the page.
+ */
+export function CopilotPanelRotateColors() {
+ const [wallpaper, modifyWallpaper] = useCurrentWallpaper();
+ const modifyWallpaperFont = useCallback(() => {
+ modifyWallpaper((modifiedWallpaper) => {
+ modifiedWallpaper.colorStats.palette = rotateItems(modifiedWallpaper.colorStats.palette, { count: -1 });
+ modifiedWallpaper.saveStage = 'EDITED';
+
+ return modifiedWallpaper;
+ });
+ }, [modifyWallpaper]);
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/components/DeviceIframe/DeviceIframe.module.css b/src/components/DeviceIframe/DeviceIframe.module.css
index f3aa3a96f1..9d9e610eb1 100644
--- a/src/components/DeviceIframe/DeviceIframe.module.css
+++ b/src/components/DeviceIframe/DeviceIframe.module.css
@@ -10,7 +10,7 @@
perspective: 1000px;
}
-.DeviceIframe iframe {
+.DeviceIframe .iframe {
/*/
outline: 1px dotted rgb(103, 223, 79);
/**/
@@ -18,7 +18,7 @@
width: 100%;
height: 100%;
- box-shadow: 0 0 50px rgba(126, 126, 126, 0.452);
+ box-shadow: 0 0 80px rgb(0, 0, 0);
transform: scale(0.8) rotateY(15deg);
transition: transform 0.5s ease-in-out;
@@ -30,6 +30,6 @@
/**/
}
-.DeviceIframe:hover iframe {
+.DeviceIframe:hover .iframe {
transform: scale(0.9) rotateY(0deg);
}
diff --git a/src/components/DeviceIframe/DeviceIframe.tsx b/src/components/DeviceIframe/DeviceIframe.tsx
index 5e685a1ceb..fa2efad451 100644
--- a/src/components/DeviceIframe/DeviceIframe.tsx
+++ b/src/components/DeviceIframe/DeviceIframe.tsx
@@ -1,5 +1,10 @@
import Link from 'next/link';
+import { useState } from 'react';
+import { IS_DEVELOPMENT } from '../../../config';
import { classNames } from '../../utils/classNames';
+import { Color } from '../../utils/color/Color';
+import { textColor } from '../../utils/color/operators/furthest';
+import type { WithTake } from '../../utils/take/interfaces/ITakeChain';
import { string_css_class, string_url } from '../../utils/typeAliases';
import styles from './DeviceIframe.module.css';
@@ -19,18 +24,53 @@ interface DeviceIframeProps {
* Optional CSS class name
*/
className?: string_css_class;
+
+ /**
+ * Placeholder color before the iframe is loaded
+ */
+ color?: WithTake
;
}
/**
* Renders an iframe based on the given props
*/
export function DeviceIframe(props: DeviceIframeProps) {
- const { src, isInteractive, className } = props;
+ const { src, isInteractive, className, color } = props;
- if (isInteractive) {
+ const [isIframeShownInDevelopment, setIframeShownInDevelopment] = useState(false);
+
+ if (IS_DEVELOPMENT && !isIframeShownInDevelopment) {
return (
-
-
+
setIframeShownInDevelopment(true)}
+ >
+
+ Due to optimization NOT showing the {''} in development until you click here
+
+
+ );
+ } else if (isInteractive) {
+ return (
+
+
);
} else {
@@ -40,7 +80,13 @@ export function DeviceIframe(props: DeviceIframeProps) {
href={src}
prefetch={false /* <- Note: Because already prefetching by rendering
*/}
>
-
+
);
}
diff --git a/src/components/Dialogues/Dialogues.tsx b/src/components/Dialogues/Dialogues.tsx
deleted file mode 100644
index 21a2cebaf9..0000000000
--- a/src/components/Dialogues/Dialogues.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-import { Modal } from '../Modal/00-Modal';
-import styles from './Dialogues.module.css';
-import { IPromptInQueue } from './dialogues/promptDialogue';
-import { isDialoguesRendered } from './locks/Dialogues.lock';
-import { promptDialogueQueue } from './queues/prompts';
-
-/**
- * Renders a place where the dialogues are rendered
- *
- * The component is initially hidden and is shown when the first dialogue is rendered
- * Note: There can be only one instance of this component in the app
- */
-export function Dialogues() {
- useEffect(
- () => {
- if (isDialoguesRendered.value === true) {
- throw new Error('There can be only one instance of TasksInProgress in the app');
- }
- isDialoguesRendered.value = true;
- return () => {
- isDialoguesRendered.value = false;
- };
- },
- [
- // Note: Check only once on mount
- ],
- );
-
- const [currentPromptInQueue, setCurrentPromptInQueue] = useState
(null);
- const textareaRef = useRef(null);
-
- useEffect(() => {
- if (currentPromptInQueue) {
- return;
- }
-
- const interval = setInterval(() => {
- const promptInQueue = promptDialogueQueue.find((promptInQueue) => promptInQueue.answer === undefined);
-
- if (!promptInQueue) {
- return;
- }
-
- setCurrentPromptInQueue(promptInQueue);
- if (textareaRef.current) {
- textareaRef.current.value = promptInQueue.defaultValue || '';
- }
- }, 50 /* <- TODO: POLLING_INTERVAL_MS into config */);
-
- return () => {
- clearInterval(interval);
- };
- }, [currentPromptInQueue, textareaRef]);
-
- if (!currentPromptInQueue) {
- return null;
- }
-
- return (
-
-
- );
-}
-
-/**
- * TODO: !! Is overy answer recorded and in order?
- * TODO: Spelling dialog vs dialogue ACRY
- * TODO: [🔏] DRY Locking mechanism | useLock hook
- */
diff --git a/src/components/Dialogues/dialogues/promptDialogue.tsx b/src/components/Dialogues/dialogues/promptDialogue.tsx
deleted file mode 100644
index 89fdfbf0f3..0000000000
--- a/src/components/Dialogues/dialogues/promptDialogue.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { forTime } from 'waitasecond';
-import { isRunningInWebWorker } from '../../../utils/isRunningInWhatever';
-import { message } from '../../../utils/typeAliases';
-import { IMessageMainToWorker, IMessagePromptDialogue } from '../../../workers/0-Workerify/PostMessages';
-import { isDialoguesRendered } from '../locks/Dialogues.lock';
-import { promptDialogueQueue } from '../queues/prompts';
-
-export interface IPromptDialogueOptions {
- /**
- * Prompt message
- *
- * Note: This is not a prompt to language model but a prompt to the user
- */
- prompt: message;
-
- /**
- * Default value for the input/textarea
- */
- defaultValue: string | null;
-
- /**
- * Placeholder for the input/textarea
- */
- placeholder?: string;
-}
-
-/**
- * Represents a prompt message that is waiting for an answer or is already answered
- *
- * Note: This is not a prompt to language model but a prompt to the user
- * @private Use only withing the folder Dialogues
- */
-export interface IPromptInQueue extends IPromptDialogueOptions {
- /**
- * Answer to the prompt
- *
- * - `undefined` means that the prompt is not answered yet and is waiting for an answer
- * - `null` means that the prompt is answered with `null`
- * - `string` means the answer to the prompt
- */
- answer: undefined | string | null;
-}
-
-/**
- * Pops up the co-pilot panel with a prompt dialogue.
- */
-export async function promptDialogue(options: IPromptDialogueOptions): Promise {
- const { prompt, defaultValue, placeholder } = options;
-
- const promptInQueue: IPromptInQueue = {
- prompt,
- defaultValue,
- placeholder,
- answer: undefined,
- };
-
- if (isRunningInWebWorker()) {
- // [🌴]
- postMessage({
- type: 'PROMPT_DIALOGUE',
- promptOptions: { prompt, defaultValue, placeholder },
- } satisfies IMessagePromptDialogue);
-
- return new Promise((resolve) => {
- const onMessage = (event: MessageEvent>) => {
- const message = event.data;
- if (message.type !== 'PROMPT_DIALOGUE_ANSWER') {
- return;
- }
- resolve(message.promptAnswer);
- removeEventListener('message', onMessage);
- };
- addEventListener('message' /* <-[👂][0] */, onMessage);
- });
- }
-
- if (isDialoguesRendered.value === false) {
- throw new Error(' component is not rendered');
- }
-
- promptDialogueQueue.push(promptInQueue);
-
- // console.info('❔ promptDialogue: Waiting for answer', { prompt });
-
- while (true) {
- await forTime(50 /* <- TODO: POLLING_INTERVAL_MS into config */);
-
- if (promptInQueue.answer !== undefined) {
- const answer = promptInQueue.answer;
- // console.info('❔ promptDialogue: Have answer', { prompt, answer });
- return answer;
- }
- }
-}
-
-/**
- * TODO: !! JSX must work in worker OR Should not be possible to use JSX from worker OR at all
- * TODO: Break in some timeout
- * TODO: Use some better forValueDefined
- * TODO: isMultiline
- * TODO: [🍁] Put unique ID to each prompt
- * TODO: [🌴] There is not ideally separated responsibilities between Workerify and dialogues - Either Workerify should not know about dialogues OR dialogues should not know about Workerify
- */
diff --git a/src/components/Dialogues/queues/TODO.txt b/src/components/Dialogues/queues/TODO.txt
deleted file mode 100644
index 4aa5c2a9d5..0000000000
--- a/src/components/Dialogues/queues/TODO.txt
+++ /dev/null
@@ -1 +0,0 @@
-TODO: Maybe implement promptDialogueQueue via Promise, React.Context, RxJS or something better than polling from singleton
\ No newline at end of file
diff --git a/src/components/Dialogues/queues/prompts.ts b/src/components/Dialogues/queues/prompts.ts
deleted file mode 100644
index 38e02c1dc8..0000000000
--- a/src/components/Dialogues/queues/prompts.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { IPromptInQueue } from "../dialogues/promptDialogue";
-
-/**
- * Queue of prompt dialogues that are waiting for an answer
- *
- * @private Use only withing the folder Dialogues
- */
-export const promptDialogueQueue: Array = [];
diff --git a/src/components/Whois/AdvancedDomainsChecker.module.css b/src/components/Domains/AdvancedDomainsChecker.module.css
similarity index 100%
rename from src/components/Whois/AdvancedDomainsChecker.module.css
rename to src/components/Domains/AdvancedDomainsChecker.module.css
diff --git a/src/components/Whois/AdvancedDomainsChecker.tsx b/src/components/Domains/AdvancedDomainsChecker.tsx
similarity index 84%
rename from src/components/Whois/AdvancedDomainsChecker.tsx
rename to src/components/Domains/AdvancedDomainsChecker.tsx
index bca9fe2e40..27d3b1a2b2 100644
--- a/src/components/Whois/AdvancedDomainsChecker.tsx
+++ b/src/components/Domains/AdvancedDomainsChecker.tsx
@@ -2,16 +2,15 @@ import { useState } from 'react';
import spaceTrim from 'spacetrim';
import { string_domain, string_tdl } from '../../utils/typeAliases';
import styles from './AdvancedDomainsChecker.module.css';
+import { DomainsStatusList } from './DomainsStatusList/DomainsStatusList';
import { createAllPermutationsOf } from './utils/createAllPermutationsOf';
import { createAllSubsetsOf } from './utils/createAllSubsetsOf';
-import { WhoisDomains } from './WhoisDomains/WhoisDomains';
-
/**
* Renders a domain checker with advanced options and patterns
*/
export function AdvancedDomainsChecker() {
- const [names, setNames] = useState>(['ai', 'project']);
+ const [names, setNames] = useState>(['web', 'gpt']);
const [tdls, setTdls] = useState>(['com', /*'org', 'io', 'net',*/ 'cz']);
const namePartsCombinations = createAllSubsetsOf(...names);
@@ -20,7 +19,9 @@ export function AdvancedDomainsChecker() {
subset.length === 0 ? [] : [subset.join('') /*, subset.join('-')*/],
);
- const domains = nameCombinations.flatMap((name) => tdls.map((tdl) => `${name}.${tdl}`));
+ const domains = nameCombinations
+ .flatMap((name) => tdls.map((tdl) => `${name}.${tdl}`))
+ .map((domain) => domain.trim().toLowerCase().split(' ').join('-'));
const uniqueDomains = [...new Set(domains)];
const sortedDomains = uniqueDomains.sort((a, b) => a.length - b.length);
@@ -49,7 +50,7 @@ export function AdvancedDomainsChecker() {
{JSON.stringify({ names, tdls }, null, 4)}
{/**/}
-
+
);
}
diff --git a/src/components/Whois/WhoisDomain/WhoisDomain.module.css b/src/components/Domains/DomainStatusText/DomainStatusText.module.css
similarity index 50%
rename from src/components/Whois/WhoisDomain/WhoisDomain.module.css
rename to src/components/Domains/DomainStatusText/DomainStatusText.module.css
index 092731726c..a208ed7807 100644
--- a/src/components/Whois/WhoisDomain/WhoisDomain.module.css
+++ b/src/components/Domains/DomainStatusText/DomainStatusText.module.css
@@ -1,24 +1,24 @@
-.whois .pending {
+.DomainStatusText .pending {
color: #888888;
}
-.whois .available {
+.DomainStatusText .available {
color: #0bee57;
}
-.whois .registered {
+.DomainStatusText .registered {
color: #dd1b1b;
}
-.whois .unknown {
+.DomainStatusText .unknown {
color: #888888;
}
-.whois span b {
+.DomainStatusText span b {
color: inherit;
}
-.action {
+.DomainStatusText .action {
outline: none;
border: 1px solid #ccc;
@@ -26,9 +26,11 @@
background-color: #222222;
color: #ccc;
border-radius: 3px;
- padding: 0;
- padding-left: 5px;
- padding-right: 5px;
+ padding-top: 0;
+ padding-bottom: 2px;
+ padding-left: 8px;
+ padding-right: 8px;
margin: 0;
margin-left: 5px;
+ text-decoration: none;
}
diff --git a/src/components/Whois/WhoisDomain/WhoisDomain.tsx b/src/components/Domains/DomainStatusText/DomainStatusText.tsx
similarity index 56%
rename from src/components/Whois/WhoisDomain/WhoisDomain.tsx
rename to src/components/Domains/DomainStatusText/DomainStatusText.tsx
index c3fd807f63..a3493f4284 100644
--- a/src/components/Whois/WhoisDomain/WhoisDomain.tsx
+++ b/src/components/Domains/DomainStatusText/DomainStatusText.tsx
@@ -1,32 +1,56 @@
import Link from 'next/link';
-import { useState } from 'react';
-import { string_domain } from '../../../utils/typeAliases';
-import { useWhois } from '../utils/useWhois';
-import styles from './WhoisDomain.module.css';
+import { useMemo } from 'react';
+import { classNames } from '../../../utils/classNames';
+import { checkDomain } from '../../../utils/domains/checkDomain';
+import { usePromise } from '../../../utils/hooks/usePromise';
+import { string_css_class, string_domain } from '../../../utils/typeAliases';
+import styles from './DomainStatusText.module.css';
-interface WhoisDomainProps {
+interface DomainStatusTextProps {
/**
* The domain to check
*
* Note: The domain will be normalized - trimmed and lowercased
*/
domain: string_domain;
+
+ /**
+ * Is button to open page shown?
+ */
+ isActionButtonShown?: boolean;
+
+ /**
+ * Is shown that the domain exceeded limit for whois lookups?
+ * If no or not set, it will be shown as UNKNOWN
+ */
+ isShownExceededLimit?: boolean;
+
+ /**
+ * Optional CSS class name which will be added to root element
+ */
+ className?: string_css_class;
}
/**
* Renderrs an info about a domain
+ *
* Note: It internally fetches and displays the whois
*/
-export function WhoisDomain(props: WhoisDomainProps) {
- let { domain } = props;
+export function DomainStatusText(props: DomainStatusTextProps) {
+ const { domain, isActionButtonShown, isShownExceededLimit, className } = props;
- domain = domain.trim().toLowerCase().split(' ').join('-');
+ const domainStatusPromise = useMemo(() => /* not await */ checkDomain(domain), [domain]);
+ let { value: domainStatus } = usePromise(domainStatusPromise, [domain]);
- const [nonce, setNonce] = useState(0);
- const { domainStatus, whois } = useWhois(domain, nonce);
+ if (domainStatus === 'LIMIT' && !isShownExceededLimit) {
+ domainStatus = 'UNKNOWN';
+ }
return (
- console.info(whois)} className={styles.whois}>
+
console.info(whois)}
+ className={classNames(styles.DomainStatusText, className)}
+ >
{
{
PENDING: (
@@ -55,28 +79,32 @@ export function WhoisDomain(props: WhoisDomainProps) {
{domain} status is unknown
),
- }[domainStatus]
+ }[domainStatus || 'PENDING']
}
+ {/* TODO: [🧠] How to refresh the domain information?
+ */}
+ {/* TODO: [🧠] How/where to offer domain registration?
{domainStatus === 'AVAILABLE' && (
Register
)}
+ */}
- {domainStatus === 'REGISTERED' && (
-
+ {isActionButtonShown && domainStatus === 'REGISTERED' && (
+
Open
)}
@@ -85,3 +113,7 @@ export function WhoisDomain(props: WhoisDomainProps) {
);
}
+
+/**
+ * TODO: !! Probbably debounce the whois lookup
+ */
diff --git a/src/components/Whois/WhoisDomains/WhoisDomains.module.css b/src/components/Domains/DomainsStatusList/DomainsStatusList.module.css
similarity index 91%
rename from src/components/Whois/WhoisDomains/WhoisDomains.module.css
rename to src/components/Domains/DomainsStatusList/DomainsStatusList.module.css
index 3656edd573..23f6342461 100644
--- a/src/components/Whois/WhoisDomains/WhoisDomains.module.css
+++ b/src/components/Domains/DomainsStatusList/DomainsStatusList.module.css
@@ -1,4 +1,4 @@
-.WhoisDomains {
+.DomainsStatusList {
/*/
outline: 1px dotted rgb(255, 38, 38);
/**/
diff --git a/src/components/Whois/WhoisDomains/WhoisDomains.tsx b/src/components/Domains/DomainsStatusList/DomainsStatusList.tsx
similarity index 53%
rename from src/components/Whois/WhoisDomains/WhoisDomains.tsx
rename to src/components/Domains/DomainsStatusList/DomainsStatusList.tsx
index 2e426be11e..531bc0d8d9 100644
--- a/src/components/Whois/WhoisDomains/WhoisDomains.tsx
+++ b/src/components/Domains/DomainsStatusList/DomainsStatusList.tsx
@@ -1,8 +1,8 @@
import { string_domain } from '../../../utils/typeAliases';
-import { WhoisDomain } from '../WhoisDomain/WhoisDomain';
-import styles from './WhoisDomains.module.css';
+import { DomainStatusText } from '../DomainStatusText/DomainStatusText';
+import styles from './DomainsStatusList.module.css';
-interface WhoisDomainsProps {
+interface DomainsStatusListProps {
/**
* The domains to check
*
@@ -15,13 +15,13 @@ interface WhoisDomainsProps {
* Renderrs an info about multiple domains
* Note: It internally fetches and displays the whois
*/
-export function WhoisDomains(props: WhoisDomainsProps) {
+export function DomainsStatusList(props: DomainsStatusListProps) {
const { domains } = props;
return (
-
+
{domains.map((domain) => (
-
+
))}
);
diff --git a/src/components/Whois/SimpleDomainChecker.tsx b/src/components/Domains/SimpleDomainChecker.tsx
similarity index 74%
rename from src/components/Whois/SimpleDomainChecker.tsx
rename to src/components/Domains/SimpleDomainChecker.tsx
index 2a593b5c58..71e8c32dbc 100644
--- a/src/components/Whois/SimpleDomainChecker.tsx
+++ b/src/components/Domains/SimpleDomainChecker.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { string_hostname } from '../../utils/typeAliases';
-import { WhoisDomain } from './WhoisDomain/WhoisDomain';
+import { DomainStatusText } from './DomainStatusText/DomainStatusText';
/**
* Renders a simple domain checker with a single input and single output
@@ -9,13 +9,13 @@ export function SimpleDomainChecker() {
const [domain, setDomain] = useState
('');
return (
-
+
setDomain(event.target.value)}
placeholder="example.com"
/>
-
+
);
}
diff --git a/src/components/Whois/utils/createAllPermutationsOf.test.ts b/src/components/Domains/utils/createAllPermutationsOf.test.ts
similarity index 100%
rename from src/components/Whois/utils/createAllPermutationsOf.test.ts
rename to src/components/Domains/utils/createAllPermutationsOf.test.ts
diff --git a/src/components/Whois/utils/createAllPermutationsOf.ts b/src/components/Domains/utils/createAllPermutationsOf.ts
similarity index 100%
rename from src/components/Whois/utils/createAllPermutationsOf.ts
rename to src/components/Domains/utils/createAllPermutationsOf.ts
diff --git a/src/components/Whois/utils/createAllSubsetsOf.test.ts b/src/components/Domains/utils/createAllSubsetsOf.test.ts
similarity index 100%
rename from src/components/Whois/utils/createAllSubsetsOf.test.ts
rename to src/components/Domains/utils/createAllSubsetsOf.test.ts
diff --git a/src/components/Whois/utils/createAllSubsetsOf.ts b/src/components/Domains/utils/createAllSubsetsOf.ts
similarity index 100%
rename from src/components/Whois/utils/createAllSubsetsOf.ts
rename to src/components/Domains/utils/createAllSubsetsOf.ts
diff --git a/src/components/EditContentModal/EditContentModal.tsx b/src/components/EditContentModal/EditContentModal.tsx
index 5dc0c54a53..cbfa0ad967 100644
--- a/src/components/EditContentModal/EditContentModal.tsx
+++ b/src/components/EditContentModal/EditContentModal.tsx
@@ -1,5 +1,6 @@
import MonacoEditor from '@monaco-editor/react';
import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper';
+import { validateMaxdown } from '../Content/Maxdown/validateMaxdown';
import { Modal } from '../Modal/00-Modal';
import styles from './EditContentModal.module.css';
@@ -24,7 +25,7 @@ export function EditContentModal() {
return;
}
modifyWallpaper((modifiedWallpaper) => {
- modifiedWallpaper.content = newContent;
+ modifiedWallpaper.content = validateMaxdown(newContent);
modifiedWallpaper.saveStage = 'EDITED';
return modifiedWallpaper;
});
diff --git a/src/components/ExportModal/ExportModal.module.css b/src/components/ExportModal/ExportModal.module.css
index 5196b76151..5265ec7288 100644
--- a/src/components/ExportModal/ExportModal.module.css
+++ b/src/components/ExportModal/ExportModal.module.css
@@ -4,7 +4,7 @@
/**/
width: 100%;
- height: calc(100% - 100px);
+ height: min-content;
/*background-color: rgba(41, 81, 168, 0.308);*/
diff --git a/src/components/ExportModal/ExportModal.tsx b/src/components/ExportModal/ExportModal.tsx
index 8ab4ac907e..3b479a1ce4 100644
--- a/src/components/ExportModal/ExportModal.tsx
+++ b/src/components/ExportModal/ExportModal.tsx
@@ -1,14 +1,14 @@
-import { useRouter } from 'next/router';
-import { useState } from 'react';
+import { FormEvent, useCallback, useState } from 'react';
+import spaceTrim from 'spacetrim';
import { classNames } from '../../utils/classNames';
import { useCurrentWallpaper } from '../../utils/hooks/useCurrentWallpaper';
import { provideClientEmail } from '../../utils/supabase/provideClientEmail';
import { string_email } from '../../utils/typeAliases';
import { isValidUrl } from '../../utils/validators/isValidUrl';
-import { MarkdownContent } from '../MarkdownContent/MarkdownContent';
+import { MarkdownContent } from '../Content/MarkdownContent';
+import { GetTheWebTabs } from '../GetTheWebTabs/GetTheWebTabs';
import { Modal } from '../Modal/00-Modal';
import { PricingPlan, PricingPlans } from '../PricingTable/plans';
-import { GetTheWebTabs } from '../PublishModal/GetTheWebTabs';
import { Select } from '../Select/Select';
import stylesForSelect from '../Select/Select.module.css';
import { WallpaperLink } from '../WallpaperLink/WallpaperLink';
@@ -36,7 +36,6 @@ export const ExportSystem = {
* Renders the main export modal
*/
export function ExportModal() {
- const router = useRouter();
const [wallpaper] = useCurrentWallpaper();
const [publicUrl, setPublicUrl] = useState
(null);
const [email, setEmail] = useState(provideClientEmail() || '');
@@ -44,35 +43,65 @@ export function ExportModal() {
const [system, setSystem] = useState('STATIC');
const [plan, setPlan] = useState('SIMPLE');
const [isHelpNeeded, setHelpNeeded] = useState(false);
+ const [isExporting, setExporting] = useState(false);
+ const submitHandler = useCallback(
+ async (event: FormEvent) => {
+ event.preventDefault();
+
+ if (isExporting) {
+ alert(`Please wait until the website is exported`);
+ return;
+ }
+
+ if (publicUrl === null) {
+ alert(`Please enter the website url`);
+ return;
+ }
+
+ setExporting(true);
+
+ try {
+ await exportWebsite({
+ wallpaper,
+ publicUrl,
+ email,
+ system,
+ plan,
+ isHelpNeeded,
+ });
+ } catch (error) {
+ if (!(error instanceof Error)) {
+ throw error;
+ }
+
+ alert(
+ // <- TODO: Use here alertDialogue
+ // TODO: [🦻] DRY User error message
+ spaceTrim(`
+ Sorry for the inconvenience 😔
+ Something went wrong while making your website.
+ Please try it again or write me an email to me@pavolhejny.com
+
+ ${error.message}
+ `),
+ );
+ } finally {
+ setExporting(false);
+ // TODO: Maybe reset the form
+ }
+ },
+ [isExporting, publicUrl, email, system, plan, isHelpNeeded, wallpaper],
+ );
return (
-