Jimmy Chion 3 years ago
parent
commit
43df1b95b4

+ 29 - 92
src/components/CodeBlocks/Css.tsx

@@ -1,49 +1,34 @@
-import { Form, Switch, Select } from 'antd';
+import { Form, Switch } from 'antd';
 import hljs from 'highlight.js/lib/core';
 import React from 'react';
-import styled from 'styled-components';
+// import styled from 'styled-components';
 import shallow from 'zustand/shallow';
-import { breakpoints } from '../layout';
 import { rgbToString } from './Output';
-import { SectionTitle, SliderInput, ChromePickerColor, ColorPicker } from './subcomponents';
-import { ColorType, useInputStore } from '~/components/store';
+import { SectionTitle } from './subcomponents';
+import { GradientPicker } from '~/components/GradientPicker';
+import { AnyGradientType, useInputStore } from '~/components/store';
 
 export const CssControls: React.FC = () => {
-  const [setCssProps, cssProps, filterProps] = useInputStore(
-    (state) => [state.setCssProps, state.cssProps, state.filterProps],
+  const [cssProps, filterProps] = useInputStore(
+    (state) => [state.cssProps, state.filterProps],
     shallow
   );
   const { brightness, contrast } = filterProps;
-  const { gradientType, color1, color2, angle, showTransparency, posX, posY } = cssProps;
+  const { gradients, showTransparency } = cssProps;
 
-  const setValues = (key: string, value: number | string | ColorType | boolean) => {
-    const toSet = {
-      gradientType,
-      color1,
-      color2,
-      angle,
-      showTransparency,
-      posX,
-      posY,
-    };
-    toSet[key] = value;
-    setCssProps(toSet);
-  };
+  //temp
+  const firstGradient = gradients[0];
+  const { type: gradientType, stops } = firstGradient;
 
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const gradientFirstParam = getGradientFirstParam(firstGradient);
 
   const gradientCss = `/* css gradient: second layer */
 {
   width: 250px;
   height: 250px;
-  background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )})${showTransparency ? ', url(/checkers.png)' : ''};
+  background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(
+    stops[0].color
+  )}, ${rgbToString(stops[1].color)})${showTransparency ? ', url(/checkers.png)' : ''};
   /* filter: contrast(${contrast}%) brightness(${brightness}%); */
 }`;
   return (
@@ -57,68 +42,15 @@ export const CssControls: React.FC = () => {
           }}
         />
       </pre>
+      <GradientPicker />
       <Form>
-        <GradientControls>
-          <Form.Item label="Gradient type">
-            <Select
-              value={gradientType}
-              onChange={(v: string) => setValues('gradientType', v)}
-              style={{ width: 120 }}
-            >
-              <Select.Option value="linear">linear</Select.Option>
-              <Select.Option value="radial">radial</Select.Option>
-              <Select.Option value="conic">conic</Select.Option>
-            </Select>
-          </Form.Item>
-          <ColorPicker
-            label="Color 1"
-            color={color1}
-            style={{ padding: 0 }}
-            onChange={(c: ChromePickerColor) => setValues('color1', c.rgb)}
-          />
-          <ColorPicker
-            label="Color 2"
-            color={color2}
-            onChange={(c: ChromePickerColor) => setValues('color2', c.rgb)}
-          />
-        </GradientControls>
-
-        {['linear', 'conic'].includes(gradientType) && (
-          <SliderInput
-            label="angle"
-            name="angle"
-            min={0}
-            max={360}
-            tipFormatter={(v) => `${v}°`}
-            onChange={(val: number) => setValues('angle', val)}
-            value={typeof angle === 'number' ? angle : 0}
-          />
-        )}
-        {['radial', 'conic'].includes(gradientType) && (
-          <>
-            <SliderInput
-              label="position X"
-              name="position X"
-              min={-50}
-              max={150}
-              onChange={(val: number) => setValues('posX', val)}
-              value={typeof posX === 'number' ? posX : 0}
-            />
-            <SliderInput
-              label="position Y"
-              name="position Y"
-              min={-50}
-              max={150}
-              onChange={(val: number) => setValues('posY', val)}
-              value={typeof posY === 'number' ? posY : 0}
-            />
-          </>
-        )}
         <Form.Item label="Show checkered">
           <Switch
             size="small"
             checked={showTransparency}
-            onChange={(e) => setValues('showTransparency', e)}
+            onChange={(e) => {
+              console.log(e);
+            }}
           />
         </Form.Item>
       </Form>
@@ -126,9 +58,14 @@ export const CssControls: React.FC = () => {
   );
 };
 
-const GradientControls = styled.div`
-  display: flex;
-  @media screen and (max-width: ${breakpoints.md - 1}px) {
-    flex-direction: column;
+export const getGradientFirstParam = (grad: AnyGradientType): string => {
+  if ('angle' in grad && grad.type === 'linear') {
+    return `${grad.angle}deg`;
+  } else if (grad.type === 'radial' && 'posX' in grad) {
+    return `circle at ${grad.posX}% ${grad.posY}%`;
+  } else if (grad.type === 'conic' && 'angle' in grad && 'posX' in grad) {
+    return `from ${grad.angle}deg at ${grad.posX}% ${grad.posY}%`;
+  } else {
+    return '';
   }
-`;
+};

+ 10 - 10
src/components/CodeBlocks/Filter.tsx

@@ -1,3 +1,4 @@
+import { getGradientFirstParam } from '.';
 import { Form, Switch } from 'antd';
 import hljs from 'highlight.js/lib/core';
 import React, { useState } from 'react';
@@ -15,13 +16,10 @@ export const FilterControls: React.FC = () => {
   const [inlineSvg, setInlineSvg] = useState(false);
   const { size, baseFrequency, numOctaves } = svgProps;
 
-  const { gradientType, angle, color1, color2, posX, posY } = cssProps;
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const { gradients } = cssProps;
+  const firstGradient = gradients[0];
+  const gradientFirstParam = getGradientFirstParam(firstGradient);
+
   const cleanSvgString = `<svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'><filter id='noiseFilter'><feTurbulence type='fractalNoise' baseFrequency='${baseFrequency}' numOctaves='${numOctaves}' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#noiseFilter)'/></svg>`;
   const inlineSvgString = `url("data:image/svg+xml,${cleanSvgString.replace(
     symbols,
@@ -31,9 +29,11 @@ export const FilterControls: React.FC = () => {
 {
   width: 250px;
   height: 250px;
-  background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )}), ${inlineSvg ? inlineSvgString : 'url(/👆/that/noise.svg)'};
+  background: ${firstGradient.type}-gradient(${gradientFirstParam}, ${rgbToString(
+    firstGradient.stops[0].color
+  )}, ${rgbToString(firstGradient.stops[1].color)}), ${
+    inlineSvg ? inlineSvgString : 'url(/👆/that/noise.svg)'
+  };
   filter: contrast(${contrast}%) brightness(${brightness}%);
 }
   `;

+ 13 - 12
src/components/CodeBlocks/Output.tsx

@@ -1,3 +1,4 @@
+import { getGradientFirstParam } from '.';
 import React from 'react';
 import styled from 'styled-components';
 import shallow from 'zustand/shallow';
@@ -14,14 +15,11 @@ const Output: React.FC = () => {
     shallow
   );
   const { size, baseFrequency, numOctaves } = svgProps;
-  const { gradientType, color1, color2, angle, showTransparency, posX, posY } = cssProps;
+  const { gradients, showTransparency } = cssProps;
   const { brightness, contrast } = filterProps;
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const firstGradient = gradients[0];
+  const gradientFirstParam = getGradientFirstParam(firstGradient);
+
   const svgString = `<!-- svg: first layer -->
 <svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'>
   <filter id='noiseFilter'>
@@ -40,8 +38,8 @@ const Output: React.FC = () => {
   width: 250px;
   height: 250px;
   background: 
-    ${gradientType}-gradient(${gradientFirstParam}, 
-      ${rgbToString(color1)}, ${rgbToString(color2)})${
+    ${firstGradient.type}-gradient(${gradientFirstParam}, 
+      ${rgbToString(firstGradient.stops[0].color)}, ${rgbToString(firstGradient.stops[1].color)})${
     showTransparency ? ', url(/checkers.png)' : ''
   };
   /* filter: contrast(${contrast}%) brightness(${brightness}%); */
@@ -50,9 +48,12 @@ const Output: React.FC = () => {
   const liveCss = `
 width: 250px;
 height: 250px;
-background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )}), url("data:image/svg+xml,${svgString.replace(symbols, encodeURIComponent)}");
+background: ${firstGradient.type}-gradient(${gradientFirstParam}, ${rgbToString(
+    firstGradient.stops[0].color
+  )}, ${rgbToString(firstGradient.stops[1].color)}), url("data:image/svg+xml,${svgString.replace(
+    symbols,
+    encodeURIComponent
+  )}");
 filter: contrast(${contrast}%) brightness(${brightness}%);
 `;
 

+ 0 - 1
src/components/CodeBlocks/subcomponents/index.tsx

@@ -1,4 +1,3 @@
-export * from './ColorPicker';
 export * from './Reset';
 export * from './SectionTitle';
 export * from './SliderInput';

+ 0 - 0
src/components/CodeBlocks/subcomponents/ColorPicker.tsx → src/components/GradientPicker/ColorPicker.tsx


+ 93 - 0
src/components/GradientPicker/GradientRow.tsx

@@ -0,0 +1,93 @@
+import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
+import { Form, Select } from 'antd';
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import { ColorStopType } from '../store';
+import { ColorPicker, ChromePickerColor } from './ColorPicker';
+import { SliderInput } from '~/components/CodeBlocks/subcomponents';
+import { GradientPicker } from '~/components/GradientPicker';
+
+export const GradientRow = () => {
+  const [gradientType, setGradientType] = useState<string>('linear');
+  const [isVisible, setIsVisible] = useState<boolean>(true);
+  const [colors, setColors] = useState<ColorStopType[]>([
+    { color: { r: 0, g: 255, b: 255, a: 1 }, offset: 0 },
+    { color: { r: 0, g: 0, b: 0, a: 0 }, offset: 1.0 },
+  ]);
+  const [angle, setAngle] = useState<number>(0);
+  const [posX, setPosX] = useState<number>(50);
+  const [posY, setPosY] = useState<number>(50);
+
+  return (
+    <Form>
+      <div>
+        <VisibilityIcon onClick={() => setIsVisible((v) => !v)}>
+          {isVisible ? <EyeOutlined /> : <EyeInvisibleOutlined />}
+        </VisibilityIcon>
+        <Form.Item label="Gradient type">
+          <Select
+            value={gradientType}
+            onChange={(v: string) => setGradientType(v)}
+            style={{ width: 130 }}
+          >
+            <Select.Option value="linear">linear</Select.Option>
+            <Select.Option value="radial">radial</Select.Option>
+            <Select.Option value="conic">conic</Select.Option>
+          </Select>
+        </Form.Item>
+        <GradientPicker />
+        <ColorPicker
+          label="Color 1"
+          color={colors[0].color}
+          style={{ padding: 0 }}
+          onChange={(c: ChromePickerColor) =>
+            setColors((colors) => [{ color: c.rgb, offset: 1 }, colors[1]])
+          }
+        />
+        <ColorPicker
+          label="Color 2"
+          color={colors[colors.length - 1].color}
+          onChange={(c: ChromePickerColor) =>
+            setColors((colors) => [colors[0], { color: c.rgb, offset: 1 }])
+          }
+        />
+      </div>
+
+      {['linear', 'conic'].includes(gradientType) && (
+        <SliderInput
+          label="angle"
+          name="angle"
+          min={0}
+          max={360}
+          tipFormatter={(v) => `${v}°`}
+          onChange={(val: number) => setAngle(val)}
+          value={typeof angle === 'number' ? angle : 0}
+        />
+      )}
+      {['radial', 'conic'].includes(gradientType) && (
+        <>
+          <SliderInput
+            label="position X"
+            name="position X"
+            min={-50}
+            max={150}
+            onChange={(val: number) => setPosX(val)}
+            value={typeof posX === 'number' ? posX : 0}
+          />
+          <SliderInput
+            label="position Y"
+            name="position Y"
+            min={-50}
+            max={150}
+            onChange={(val: number) => setPosY(val)}
+            value={typeof posY === 'number' ? posY : 0}
+          />
+        </>
+      )}
+    </Form>
+  );
+};
+
+const VisibilityIcon = styled.div`
+  padding: 3px;
+`;

+ 11 - 0
src/components/GradientPicker/index.tsx

@@ -0,0 +1,11 @@
+import React from 'react';
+// import styled from 'styled-components';
+import { GradientRow } from './GradientRow';
+
+export const GradientPicker = () => {
+  return (
+    <div>
+      <GradientRow />
+    </div>
+  );
+};

+ 66 - 39
src/components/store.tsx

@@ -7,15 +7,37 @@ export type SvgPropsType = {
 };
 
 export type ColorType = { r: number; g: number; b: number; a: number };
+export type AnyGradientType = LinearGradientType | RadialGradientType | ConicGradientType;
 
 export type CssPropsType = {
-  gradientType: string;
-  angle: number;
-  color1: ColorType;
-  color2: ColorType;
   showTransparency: boolean;
+  gradients: AnyGradientType[];
+};
+
+export type ColorStopType = {
+  color: ColorType;
+  offset: number;
+};
+
+type BaseGradientType = {
+  type: string;
+  stops: ColorStopType[];
+  isVisible: boolean;
+};
+
+export type LinearGradientType = BaseGradientType & {
+  angle: number;
+};
+
+export type RadialGradientType = BaseGradientType & {
+  posX: number;
+  posY: number;
+};
+
+export type ConicGradientType = BaseGradientType & {
   posX: number;
   posY: number;
+  angle: number;
 };
 
 export type FilterPropsType = {
@@ -26,19 +48,11 @@ export type FilterPropsType = {
 
 export type InputState = {
   svgProps: SvgPropsType;
-  setSvgProps: ({ size, baseFrequency, numOctaves }: SvgPropsType) => void;
+  setSvgProps: (props: SvgPropsType) => void;
   cssProps: CssPropsType;
-  setCssProps: ({
-    gradientType,
-    angle,
-    color1,
-    color2,
-    showTransparency,
-    posX,
-    posY,
-  }: CssPropsType) => void;
+  setCssProps: (props: CssPropsType) => void;
   filterProps: FilterPropsType;
-  setFilterProps: ({ contrast, brightness, inlineSvg }: FilterPropsType) => void;
+  setFilterProps: (props: FilterPropsType) => void;
   resetAllProps: () => void;
 };
 
@@ -48,33 +62,35 @@ export const useInputStore = create<InputState>((set) => ({
     baseFrequency: 0.65,
     numOctaves: 3,
   },
-  setSvgProps: ({ size, baseFrequency, numOctaves }) =>
-    set({
-      svgProps: { size, baseFrequency, numOctaves },
-    }),
+  setSvgProps: (props) => set({ svgProps: props }),
 
   cssProps: {
-    gradientType: 'linear',
-    angle: 112,
-    color1: { r: 0, g: 0, b: 255, a: 1 },
-    color2: { r: 0, g: 0, b: 0, a: 0 },
     showTransparency: true,
-    posX: 50,
-    posY: 50,
+    gradients: [
+      {
+        type: 'linear',
+        isVisible: true,
+        angle: 0,
+        stops: [
+          {
+            color: { r: 0, g: 0, b: 255, a: 1 },
+            offset: 0,
+          },
+          {
+            color: { r: 0, g: 0, b: 0, a: 0 },
+            offset: 1,
+          },
+        ],
+      },
+    ],
   },
-  setCssProps: ({ gradientType, angle, color1, color2, showTransparency, posX, posY }) =>
-    set({
-      cssProps: { gradientType, angle, color1, color2, showTransparency, posX, posY },
-    }),
+  setCssProps: (props) => set({ cssProps: props }),
   filterProps: {
     contrast: 170,
     brightness: 1000,
     inlineSvg: false,
   },
-  setFilterProps: ({ contrast, brightness, inlineSvg }) =>
-    set({
-      filterProps: { contrast, brightness, inlineSvg },
-    }),
+  setFilterProps: (props) => set({ filterProps: props }),
   resetAllProps: () =>
     set({
       svgProps: {
@@ -83,13 +99,24 @@ export const useInputStore = create<InputState>((set) => ({
         numOctaves: 3,
       },
       cssProps: {
-        gradientType: 'linear',
-        angle: 112,
-        color1: { r: 0, g: 0, b: 255, a: 1 },
-        color2: { r: 0, g: 0, b: 0, a: 0 },
         showTransparency: true,
-        posX: 50,
-        posY: 50,
+        gradients: [
+          {
+            type: 'linear',
+            isVisible: true,
+            angle: 0,
+            stops: [
+              {
+                color: { r: 0, g: 0, b: 255, a: 1 },
+                offset: 0,
+              },
+              {
+                color: { r: 0, g: 0, b: 0, a: 0 },
+                offset: 1,
+              },
+            ],
+          },
+        ],
       },
       filterProps: {
         contrast: 170,