& { filename: string; variables: {} } {
- let queryCache = {};
-
- interface ConnectState {
- data: {};
- }
-
- class Connect extends React.Component
{
- public static filename: string;
- public static variables: Array<{
- id: string;
- query?: string;
- // TODO: Type better
- mapToCollection?: (result: any) => any;
- mapToOption?: (result: any) => { value: any; label: any };
- validation?: { type: any; values?: any; default: any };
- }>;
- public state: ConnectState = {
- data: {},
- };
- constructor(props) {
- super(props);
-
- this.state = { data: queryCache };
- }
- public componentDidMount() {
- this.fetchData().then((data) => this.setState(() => data));
- }
- public componentDidUpdate(prevProps) {
- if (!isEqual(prevProps, this.props)) {
- this.fetchData().then((data) => this.setState(() => data));
- }
- }
- public render() {
- if (this.state.data === null) {
- return null;
- }
-
- return React.createElement(component, {
- ...this.props,
- ...this.state.data,
- });
- }
- public fetchData() {
- if (propsToVars) {
- variables = {
- ...variables,
- ...propsToVars(this.props),
- };
- }
-
- return request(apiUrl, query, variables)
- .then((data) => {
- // @ts-expect-error This is fine
- queryCache = data;
-
- return { data };
- })
- .catch((err) => console.error(err));
- }
- }
-
- return Connect;
- };
-}
-
-export default connect;
diff --git a/server/routes/components/exclude-props.ts b/server/routes/components/exclude-props.ts
deleted file mode 100644
index c32f84ec..00000000
--- a/server/routes/components/exclude-props.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import omit from "lodash/omit";
-import React from "react";
-
-export default function excludeProps(exclude, element) {
- const ExcludedStyled = props =>
- React.createElement(element, omit(props, exclude));
-
- return ExcludedStyled;
-}
diff --git a/server/routes/components/index.ts b/server/routes/components/index.ts
deleted file mode 100644
index 825787e0..00000000
--- a/server/routes/components/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import Badge from "./Badge";
-
-export { Badge };
diff --git a/server/routes/components/slide-layouts/EmbedContent.tsx b/server/routes/components/slide-layouts/EmbedContent.tsx
deleted file mode 100644
index 85f40b1b..00000000
--- a/server/routes/components/slide-layouts/EmbedContent.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import styled from "@emotion/styled";
-import modularScale from "polished/lib/helpers/modularScale";
-import React from "react";
-import { Theme } from "../../../schema/Theme";
-
-const EmbedPageContainer = styled.div`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- grid-template-rows: 0.25fr 1.75fr;
- align-items: center;
-`;
-
-const Title = styled.h1`
- font-size: ${modularScale(7)};
- margin-left: 5vw;
- color: ${props => props.color};
-`;
-
-const Embed = styled.iframe`
- align-self: start;
- justify-self: center;
-`;
-
-export interface EmbedContent {
- title: string;
- link: string;
-}
-
-interface EmbedContentProps {
- content: EmbedContent;
- theme: Theme;
-}
-
-const EmbedContent = ({ content, theme }: EmbedContentProps) => (
-
- {/* @ts-expect-error This is fine */}
- {content.title}
-
-
-);
-
-export default EmbedContent;
diff --git a/server/routes/components/slide-layouts/GridContent.tsx b/server/routes/components/slide-layouts/GridContent.tsx
deleted file mode 100644
index 008e3ff8..00000000
--- a/server/routes/components/slide-layouts/GridContent.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import styled from "@emotion/styled";
-import Markdown from "markdown-to-jsx";
-import modularScale from "polished/lib/helpers/modularScale";
-import React from "react";
-import { Image } from "../../../schema/Image";
-import { Theme } from "../../../schema/Theme";
-import excludeProps from "../exclude-props";
-import getBackground from "./get-background";
-
-const GridContainer = styled(excludeProps(["background"], "div"))`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- grid-template-rows: 0.25fr 1.75fr;
- align-items: center;
- line-height: 1.5;
- background: ${({ background }) => background};
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
-`;
-
-const Title = styled.h1`
- font-size: ${modularScale(6)};
- margin-left: 5vw;
- margin-right: 5vw;
- color: ${props => props.color};
-`;
-
-const Markup = styled(excludeProps(["color"], "div"))`
- display: grid;
- grid-template-columns: 1fr 1fr;
- font-size: ${modularScale(4)};
- margin-left: 5vw;
- margin-right: 5vw;
- align-self: start;
- color: ${props => props.color};
- opacity: 0.8;
-`;
-
-const MarkdownContainer = styled.div`
- margin: 0.5em;
-`;
-
-export interface GridContent {
- columns: [string, string]; // TODO: Generalize
- title: string;
-}
-
-interface GridContentProps {
- background: Image;
- content: GridContent;
- theme: Theme;
-}
-
-const GridContent = ({ background, content, theme }: GridContentProps) => (
-
- {/* @ts-expect-error This is fine */}
-
- {content.title}
-
-
-
- {content.columns[0]}
-
-
- {content.columns[1]}
-
-
-
-);
-
-export default GridContent;
diff --git a/server/routes/components/slide-layouts/ImageContent.tsx b/server/routes/components/slide-layouts/ImageContent.tsx
deleted file mode 100644
index 000b747a..00000000
--- a/server/routes/components/slide-layouts/ImageContent.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/** @jsx jsx */
-import { jsx } from "@emotion/core";
-import styled from "@emotion/styled";
-import { Image } from "../../../schema/Image";
-
-const ImageContainer = styled.div`
- display: grid;
- min-height: 100vh;
- max-height: 100vh;
- text-align: center;
- padding: 2em;
- box-sizing: border-box;
-`;
-
-const ImageElement = styled.img`
- height: 100%;
- max-width: 100%;
- object-fit: contain;
- align-self: center;
- justify-self: center;
-`;
-
-interface ImageContentProps {
- content: Image;
- css: {};
-}
-
-const ImageContent = ({ content, css }: ImageContentProps) => (
-
-
-
-);
-
-export default ImageContent;
diff --git a/server/routes/components/slide-layouts/MarkdownContent.tsx b/server/routes/components/slide-layouts/MarkdownContent.tsx
deleted file mode 100644
index fcf7e2bc..00000000
--- a/server/routes/components/slide-layouts/MarkdownContent.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import styled from "@emotion/styled";
-import Markdown from "markdown-to-jsx";
-import modularScale from "polished/lib/helpers/modularScale";
-import React from "react";
-import { Image } from "../../../schema/Image";
-import { Theme } from "../../../schema/Theme";
-import excludeProps from "../exclude-props";
-import getBackground from "./get-background";
-
-// TODO: Allow background-size to be tuneable (contain is needed sometimes)
-const MarkdownContainer = styled(excludeProps(["background", "title"], "div"))`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- grid-template-rows: ${({ title }) => (title ? "0.25fr 1.75fr" : "1fr")};
- align-items: center;
- line-height: 1.5;
- background: ${({ background }) => background};
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
-`;
-
-const Title = styled.h1`
- font-size: ${modularScale(6)};
- margin-left: 5vw;
- margin-right: 5vw;
- color: ${props => props.color};
-`;
-
-const Markup = styled(excludeProps(["color", "title"], "div"))`
- font-size: ${modularScale(4)};
- margin-left: 5vw;
- margin-right: 5vw;
- align-self: ${({ title }) => (title ? "start" : "center")};
- color: ${({ color }) => color};
- opacity: 0.8;
-`;
-
-export interface MarkdownContent {
- title: string;
- markup: string;
-}
-
-interface MarkdownContentProps {
- background: Image;
- content: MarkdownContent;
- theme: Theme;
-}
-
-const MarkdownContent = ({
- background,
- content,
- theme,
-}: MarkdownContentProps) => (
-
- {content.title && (
- /* @ts-expect-error This is fine */
-
- {content.title}
-
- )}
-
- {content.markup}
-
-
-);
-
-export default MarkdownContent;
diff --git a/server/routes/components/slide-layouts/ReactContent.tsx b/server/routes/components/slide-layouts/ReactContent.tsx
deleted file mode 100644
index cb8c53df..00000000
--- a/server/routes/components/slide-layouts/ReactContent.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-/** @jsx jsx */
-import { jsx } from "@emotion/core";
-import styled from "@emotion/styled";
-import { Theme } from "../../../schema/Theme";
-
-const ReactPageContainer = styled.div`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- align-items: center;
- justify-items: center;
-`;
-
-interface ReactContentProps {
- content: JSX.Element;
- theme: Theme;
- css: {};
-}
-
-function ReactContent({ content, css }: ReactContentProps) {
- return {content};
-}
-
-export default ReactContent;
diff --git a/server/routes/components/slide-layouts/SectionContent.tsx b/server/routes/components/slide-layouts/SectionContent.tsx
deleted file mode 100644
index b587102a..00000000
--- a/server/routes/components/slide-layouts/SectionContent.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import styled from "@emotion/styled";
-import modularScale from "polished/lib/helpers/modularScale";
-import React from "react";
-import { Theme } from "../../../schema/Theme";
-import excludeProps from "../exclude-props";
-
-const SectionPageContainer = styled(excludeProps("backgroundColor", "div"))`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- grid-template-rows: 1fr;
- align-items: center;
- background: ${props => props.background};
-`;
-
-const Title = styled.h1`
- font-size: ${modularScale(7)};
- margin-left: 10vw;
- color: ${props => props.color};
-`;
-
-export interface SectionContent {
- title: string;
-}
-
-interface SectionContentProps {
- content: SectionContent;
- theme: Theme;
-}
-
-const SectionContent = ({ content, theme }: SectionContentProps) => (
-
- {/* @ts-expect-error This is fine */}
- {content.title}
-
-);
-
-export default SectionContent;
diff --git a/server/routes/components/slide-layouts/TitleContent.tsx b/server/routes/components/slide-layouts/TitleContent.tsx
deleted file mode 100644
index a0399bf9..00000000
--- a/server/routes/components/slide-layouts/TitleContent.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import styled from "@emotion/styled";
-import modularScale from "polished/lib/helpers/modularScale";
-import React from "react";
-import { Theme } from "../../../schema/Theme";
-
-const TitlePageContainer = styled.div`
- min-height: 100vh;
- max-height: 100vh;
- display: grid;
- align-items: center;
- justify-items: center;
-`;
-
-const Title = styled.h1`
- font-size: ${modularScale(7)};
- color: ${(props) => props.color};
- padding: 8vw;
-`;
-
-/*const Subtitle = styled.h2`
- font-size: ${modularScale(4)};
- margin-right: 5vw;
- justify-self: end;
- color: ${props => props.color};
-`;*/
-
-export interface TitleContent {
- title: JSX.Element;
-}
-
-interface TitleContentProps {
- content: TitleContent;
- theme: Theme;
-}
-
-function TitleContent({ content, theme }: TitleContentProps) {
- return (
-
- {/* @ts-expect-error This is fine */}
- {content.title}
-
- );
-}
-
-export default TitleContent;
diff --git a/server/routes/components/slide-layouts/get-background.ts b/server/routes/components/slide-layouts/get-background.ts
deleted file mode 100644
index 93d18135..00000000
--- a/server/routes/components/slide-layouts/get-background.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Image } from "../../../schema/Image";
-
-function getBackground(background: Image) {
- return background ? `${linearGradient()},url(${background.url})` : "";
-}
-
-// TODO: Make this more flexible
-function linearGradient() {
- return `linear-gradient(rgba(0, 0, 0, 0.5), rgba(65, 35, 0, 0.05))`;
-}
-
-export default getBackground;
diff --git a/server/routes/components/slide-layouts/index.ts b/server/routes/components/slide-layouts/index.ts
deleted file mode 100644
index 2ba9648c..00000000
--- a/server/routes/components/slide-layouts/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import EMBED from "./EmbedContent";
-import GRID from "./GridContent";
-import IMAGE from "./ImageContent";
-import MARKDOWN from "./MarkdownContent";
-import REACT from "./ReactContent";
-import SECTION from "./SectionContent";
-import TITLE from "./TitleContent";
-
-export default {
- IMAGE,
- EMBED,
- MARKDOWN,
- REACT,
- SECTION,
- TITLE,
- GRID,
-};
diff --git a/server/routes/date-utils.ts b/server/routes/date-utils.ts
deleted file mode 100644
index d8010f44..00000000
--- a/server/routes/date-utils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// TODO: Use browser here instead?
-function dayToFinnishLocale(day: string): string {
- const date = new Date(day);
-
- return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
-}
-
-export { dayToFinnishLocale };
diff --git a/server/routes/graphql.ts b/server/routes/graphql.ts
index ab7552f5..9f73863e 100644
--- a/server/routes/graphql.ts
+++ b/server/routes/graphql.ts
@@ -1,39 +1,30 @@
// import process from "process";
-import { createHandler as graphql } from "graphql-http/lib/use/express";
+import { createHandler as graphql } from "graphql-http/lib/use/fetch";
import depthLimit from "graphql-depth-limit";
-function routeGraphQL(router, schema, projectRoot, mediaUrl) {
- router.all(
- "/graphql",
- graphql({
- schema,
- validationRules: [depthLimit(7)],
- context: (req) => {
- // const hostname = getHostname(req);
- // @ts-expect-error This is fine
- const hostname = req.headers.host;
+function createGraphQLRequestHandler(schema, projectRoot, mediaUrl) {
+ return graphql({
+ schema,
+ validationRules: [depthLimit(7)],
+ context: (request) => {
+ const rawRequest = request.raw || request;
- return {
- hostname,
- mediaUrl: `${hostname}${mediaUrl}`,
- projectRoot,
- };
- },
- })
- );
+ return {
+ hostname: getHostname(rawRequest),
+ mediaUrl: `${getHostname(rawRequest)}${mediaUrl}`,
+ projectRoot,
+ };
+ },
+ });
}
-// TODO: Move to utils
-/*
-function getHostname(req) {
- if (process.env.HEROKU_HOSTNAME) {
- return process.env.HEROKU_HOSTNAME;
- }
+function getHostname(request: { headers: Headers; url: string }) {
+ const forwardedProtocol = request.headers.get("x-forwarded-proto");
+ const protocol =
+ forwardedProtocol || new URL(request.url).protocol.replace(":", "");
+ const host = request.headers.get("host") || new URL(request.url).host;
- // For some reason, protocol is http on render
- return "https://" + req.get("host");
- // return req.protocol + "://" + req.get("host");
+ return `${protocol}://${host}`;
}
- */
-export default routeGraphQL;
+export default createGraphQLRequestHandler;
diff --git a/server/routes/index.ts b/server/routes/index.ts
deleted file mode 100644
index 3806886a..00000000
--- a/server/routes/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import cors from "cors";
-import express from "express";
-import * as path from "path";
-import generateSchema from "../schema";
-import routeAssetDesigner from "./asset-designer";
-import routeCalendar from "./calendar";
-import routeGraphQL from "./graphql";
-import routeMedia from "./media";
-import routePing from "./ping";
-
-// FIXME: Resolve media path against project root, not module as this is brittle
-const projectRoot = path.resolve(__dirname, "../../../");
-
-async function createRouter() {
- // @ts-ignore
- const router = new express.Router();
- const schema = await generateSchema();
- const mediaUrl = "/media";
- const mediaPath = path.join(projectRoot, "media");
- const scriptPath = path.join(projectRoot, ".scripts");
-
- router.use(cors());
-
- routeAssetDesigner(router, projectRoot, scriptPath);
- routeCalendar(router);
- routeGraphQL(router, schema, projectRoot, mediaUrl);
- routeMedia(router, mediaUrl, mediaPath);
- routePing(router);
- router.use("/scripts", express.static(scriptPath));
-
- return router;
-}
-
-export default createRouter;
diff --git a/server/routes/media.ts b/server/routes/media.ts
deleted file mode 100644
index 1d0c7b3a..00000000
--- a/server/routes/media.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as path from "path";
-import logger from "../logger";
-
-function routeMedia(router, mediaUrl, mediaPath) {
- router.all(`${mediaUrl}/*`, async (req, res, next) => {
- const asset = req.params["0"];
-
- if ([".jpg", ".png", ".svg"].includes(path.extname(asset))) {
- try {
- return res.sendFile(path.join(mediaPath, asset));
- } catch (err) {
- logger.error(err);
-
- next();
- }
- } else {
- res.sendFile(asset, { root: mediaPath }, (err) => {
- res.end();
-
- if (err) {
- logger.error(err);
- }
- });
- }
- });
-}
-
-export default routeMedia;
diff --git a/server/routes/pages/AssetDesignerPage.tsx b/server/routes/pages/AssetDesignerPage.tsx
deleted file mode 100644
index 6ab53195..00000000
--- a/server/routes/pages/AssetDesignerPage.tsx
+++ /dev/null
@@ -1,457 +0,0 @@
-import styled from "@emotion/styled";
-import { Color, WidthProperty } from "csstype";
-import { saveAs } from "file-saver";
-import { createBrowserHistory as createHistory } from "history";
-import fromPairs from "lodash/fromPairs";
-import get from "lodash/get";
-import map from "lodash/map";
-import set from "lodash/set";
-import queryString from "query-string";
-import * as React from "react";
-import domToImage from "retina-dom-to-image";
-import { Theme } from "../../schema/Theme";
-import * as components from "../components";
-import connect from "../components/connect";
-import GlobalStyles from "../components/GlobalStyles";
-import Select from "../components/Select";
-import VariableSelector from "../components/VariableSelector";
-import { themesQuery } from "../queries";
-import * as templates from "../templates";
-
-interface AssetDesignerContainerProps {
- width: WidthProperty;
-}
-
-// @ts-expect-error This is fine
-const AssetDesignerContainer = styled.article`
- background: #ddd;
- display: grid;
- grid-template-columns: ${({ width }) => width} 1fr;
-
- @media print {
- display: auto;
- grid-template-columns: auto;
- }
-` as React.FC;
-
-interface SidebarProps {
- backgroundColor: Color;
-}
-
-// @ts-expect-error This is fine
-const Sidebar = styled.aside`
- padding: 1em;
- vertical-align: top;
- height: 100vh;
- position: sticky;
- background-color: ${({ backgroundColor }) => backgroundColor};
-
- @media print {
- display: none;
- }
-` as React.FC;
-const SidebarHeader = styled.h2``;
-const SidebarItem = styled.div`
- margin-bottom: 1em;
-`;
-
-const Main = styled.main`
- overflow: auto;
- margin: auto;
- align-self: center;
-`;
-
-const ExportButton = styled.button``;
-
-const VariableContainer = styled.div``;
-
-// TODO: Share the type from the backend
-interface DesignerState {
- themeId: Theme["id"];
- selectionId: string; // One of templates
- showSidebar: boolean;
- variables: { [key: string]: any };
-}
-
-enum ActionTypes {
- UPDATE_SELECTION_ID,
- UPDATE_THEME_ID,
- UPDATE_VARIABLE,
- TOGGLE_SIDEBAR,
-}
-
-function assetDesignerReducer(state: DesignerState, action) {
- const { field, value } = action;
-
- switch (action.type) {
- case ActionTypes.UPDATE_SELECTION_ID:
- updateQuery("selectionId", value);
-
- return {
- ...state,
- selectionId: value,
- variables: initializeVariables(value),
- };
- case ActionTypes.UPDATE_THEME_ID:
- updateQuery("themeId", value);
-
- return { ...state, themeId: value };
- case ActionTypes.UPDATE_VARIABLE:
- const newVariables = { ...state.variables };
-
- // Needed to support nested access (mutates!)
- set(newVariables, field, value);
-
- updateQuery("variables", JSON.stringify(newVariables));
-
- return { ...state, variables: newVariables };
- case ActionTypes.TOGGLE_SIDEBAR:
- const showSidebar = !state.showSidebar;
-
- updateQuery("showSidebar", showSidebar);
-
- return { ...state, showSidebar };
- default:
- throw new Error("No matching reducer found!");
- }
-}
-
-function updateQuery(field: string, value: any) {
- const history = createHistory();
- const query = queryString.stringify({
- ...queryString.parse(location.search),
- [field]: value,
- });
- history.push(`?${query}`);
-}
-
-interface AssetDesignerPageProps {
- initialState: {
- selectionId: DesignerState["selectionId"];
- showSidebar: boolean;
- };
- themes: Theme[];
-}
-
-function AssetDesignerPage({
- initialState = {
- selectionId: "",
- showSidebar: true,
- },
- themes,
-}: AssetDesignerPageProps) {
- if (!themes) {
- return null;
- }
-
- // TODO: How to make sure these won't capture from inputs?
- React.useEffect(() => {
- window.addEventListener("keydown", handleUserKeyPress);
-
- return () => {
- window.removeEventListener("keydown", handleUserKeyPress);
- };
- }, [handleUserKeyPress]);
-
- function handleUserKeyPress({ key }) {
- if (key === "s") {
- dispatch({ type: ActionTypes.TOGGLE_SIDEBAR });
- }
- }
-
- const [state, dispatch] = React.useReducer(
- assetDesignerReducer,
- initialState,
- ({ selectionId }) => ({
- selectionId,
- showSidebar: initializeShowSidebar(),
- themeId: initializeThemeId(),
- variables: initializeVariables(selectionId),
- })
- );
- const theme = themes.find(({ id }) => id === state.themeId) || themes[0];
- const { selectionId, showSidebar } = state;
-
- const selection = getSelection(selectionId) || NoSelectionFound;
- const sideBarWidth = showSidebar ? "18em" : 0;
- const assetDesignTemplateId = "asset-design-template-id";
-
- return (
-
-
- {showSidebar && (
-
- dispatch({ type: ActionTypes.UPDATE_THEME_ID, field, value })
- }
- onUpdateSelection={(value) =>
- dispatch({ type: ActionTypes.UPDATE_SELECTION_ID, value })
- }
- onUpdateVariable={(field, value) =>
- dispatch({ type: ActionTypes.UPDATE_VARIABLE, field, value })
- }
- />
- )}
-
- {React.createElement(selection, {
- ...state.variables,
- theme,
- sideBarWidth,
- id: assetDesignTemplateId,
- })}
-
-
- );
-}
-
-function initializeShowSidebar() {
- return Boolean(get(queryString.parse(location.search), "showSidebar", true));
-}
-
-function initializeThemeId() {
- return get(queryString.parse(location.search), "themeId", `""`);
-}
-
-function initializeVariables(selectionId) {
- const selection = getSelection(selectionId) || {};
- const queryParameters = get(
- queryString.parse(location.search),
- "variables",
- "{}"
- );
- let queryVariables = {};
-
- try {
- // TODO: Figure out how to eliminate "as"
- queryVariables = JSON.parse(queryParameters as string);
- } catch (err) {
- console.log(err);
- }
-
- return {
- ...fromPairs(
- map(selection.variables, ({ id, validation }) => {
- return [id, get(validation, "default")];
- })
- ),
- ...queryVariables,
- };
-}
-
-function getSelection(selectionId) {
- return templates[selectionId] || components[selectionId];
-}
-
-function NoSelectionFound() {
- return <>No selection found!>;
-}
-
-interface AssetDesignerSidebarProps {
- themes: Theme[];
- theme: Theme;
- assetDesignTemplateId: string;
- selection: any; // TODO: React component with meta
- selectionId: DesignerState["selectionId"];
- variables: DesignerState["variables"];
- onUpdateTheme: (field, value) => void;
- onUpdateSelection: (value) => void;
- onUpdateVariable: (field, value) => void;
-}
-
-function AssetDesignerSidebar({
- themes,
- theme,
- assetDesignTemplateId,
- selection,
- selectionId,
- variables,
- onUpdateTheme,
- onUpdateSelection,
- onUpdateVariable,
-}: AssetDesignerSidebarProps) {
- const selectionVariables = selection.variables;
- const variableOptions = selectionVariables
- ? map(selectionVariables, (variable) => ({
- ...variable,
- value: variables[variable.id],
- }))
- : []; // TODO: Overlay to selection
-
- return (
-
- Asset designer
-
-
- {
- const domNode = document.getElementById(assetDesignTemplateId);
-
- if (domNode) {
- domToImage
- .toBlob(domNode)
- .then((blob) => {
- // TODO: Improve this further (i.e. name of the speaker for tweets etc.)
- saveAs(blob, `${selection.filename}.png`);
- })
- .catch((err) => console.error(err));
- }
- }}
- >
- Export image as PNG
-
-
-
-
- {
- const domNode = document.getElementById(assetDesignTemplateId);
-
- if (domNode) {
- domToImage
- .toSvg(domNode)
- .then((svg) => {
- // TODO: Improve this further (i.e. name of the speaker for tweets etc.)
- saveAs(svg, `${selection.filename}.svg`);
- })
- .catch((err) => console.error(err));
- }
- }}
- >
- Export image as SVG
-
-
-
-
- Themes
-
-
-
-
- Templates
-
-
-
-
- Components
-
-
-
- {variableOptions.length > 0 && (
-
- Variables
-
- {map(variableOptions, (variable) => (
-
-
-
- ))}
-
- )}
-
- );
-}
-
-interface ThemeSelectorProps {
- themes: Theme[];
- selectedTheme: Theme["id"];
- onChange: (field: string, value: string) => void;
-}
-
-function ThemeSelector({
- themes,
- selectedTheme,
- onChange,
-}: ThemeSelectorProps) {
- return (
-