JsonFormatter.jsx 5.19 KB
Newer Older
fisherdaddy's avatar
fisherdaddy committed
1 2
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
fisherdaddy's avatar
fisherdaddy committed
3
import { Title, Wrapper, Container, Preview } from '../js/SharedStyles';
4
import { useTranslation } from '../js/i18n';
fisherdaddy's avatar
fisherdaddy committed
5
import SEO from '../components/SEO';
fisherdaddy's avatar
fisherdaddy committed
6

7 8 9 10 11 12 13 14 15 16
const InputText = styled.textarea`
  width: 100%;
  height: 200px;
  font-size: 14px;
  padding: 10px;
  border: none;
  border-bottom: 1px solid #e0e0e0;
  box-sizing: border-box;
  outline: none;
  resize: none;
fisherdaddy's avatar
fisherdaddy committed
17

18 19 20 21 22 23
  @media (min-width: 768px) {
    width: 35%;
    height: 100%;
    border-bottom: none;
    border-right: 1px solid #e0e0e0;
  }
fisherdaddy's avatar
fisherdaddy committed
24 25
`;

26
const PreviewContainer = styled.div`
fisherdaddy's avatar
fisherdaddy committed
27
  width: 100%;
28 29 30
  display: flex;
  flex-direction: column;
  justify-content: space-between;
fisherdaddy's avatar
fisherdaddy committed
31
  padding: 10px;
32
  box-sizing: border-box;
fisherdaddy's avatar
fisherdaddy committed
33

34 35 36 37
  @media (min-width: 768px) {
    width: 65%;
    height: 100%;
  }
fisherdaddy's avatar
fisherdaddy committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
`;

const ToggleButton = styled.span`
  cursor: pointer;
  color: #666;
  font-weight: bold;
  margin-right: 5px;
`;

const Key = styled.span`
  color: #881391;
`;

const Value = styled.span`
  color: #1a1aa6;
`;

const JsonList = styled.ul`
  list-style-type: none;
  padding-left: 20px;
  margin: 0;
`;

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
const CopyButton = styled.button`
  position: absolute;
  top: 10px;
  right: 10px;
  background-color: transparent;
  border: none;
  cursor: pointer;
  padding: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.6;
  transition: opacity 0.3s, color 0.3s;

  &:hover {
    opacity: 1;
  }

  svg {
    width: 16px;
    height: 16px;
  }

  &.copied {
    color: #34a853; // Google green color for success feedback
  }
`;

const RelativePreviewContainer = styled(PreviewContainer)`
  position: relative;
`;

fisherdaddy's avatar
fisherdaddy committed
93
function JsonFormatter() {
94
  const { t } = useTranslation();
fisherdaddy's avatar
fisherdaddy committed
95 96
  const [input, setInput] = useState('');
  const [parsedJson, setParsedJson] = useState(null);
97
  const [isCopied, setIsCopied] = useState(false);
fisherdaddy's avatar
fisherdaddy committed
98 99 100 101 102 103 104 105 106 107

  useEffect(() => {
    try {
      const parsed = JSON.parse(input);
      setParsedJson(parsed);
    } catch (error) {
      setParsedJson(null);
    }
  }, [input]);

108 109 110 111 112 113 114 115 116 117
  const handleCopy = () => {
    if (parsedJson) {
      const formattedJson = JSON.stringify(parsedJson, null, 2);
      navigator.clipboard.writeText(formattedJson).then(() => {
        setIsCopied(true);
        setTimeout(() => setIsCopied(false), 2000);
      });
    }
  };

fisherdaddy's avatar
fisherdaddy committed
118
  return (
fisherdaddy's avatar
fisherdaddy committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    <>
       <SEO
        title={t('tools.jsonFormatter.title')}
        description={t('tools.jsonFormatter.description')}
      />
      <Wrapper>
        <Title>{t('tools.jsonFormatter.title')}</Title>
        <Container>
          <InputText
            placeholder={t('tools.jsonFormatter.inputPlaceholder')}
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <RelativePreviewContainer>
            {parsedJson ? (
              <>
                <Preview>
                  <JsonView data={parsedJson} />
                </Preview>
                <CopyButton onClick={handleCopy} className={isCopied ? 'copied' : ''}>
                  {isCopied ? (
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
                    </svg>
                  ) : (
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                      <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
                    </svg>
                  )}
                </CopyButton>
              </>
            ) : (
              <Preview>{t('tools.jsonFormatter.invalidJson')}</Preview>
            )}
          </RelativePreviewContainer>
        </Container>
      </Wrapper>
    </>
fisherdaddy's avatar
fisherdaddy committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  );
}

function JsonView({ data }) {
  const [isExpanded, setIsExpanded] = useState(true);

  if (Array.isArray(data)) {
    return (
      <div>
        <ToggleButton onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? '[-]' : '[+]'}
        </ToggleButton>
        {!isExpanded && <span>Array</span>}
        {isExpanded && (
          <JsonList>
172
            [
fisherdaddy's avatar
fisherdaddy committed
173 174 175
            {data.map((item, index) => (
              <li key={index}>
                <JsonView data={item} />
176
                {index < data.length - 1 && ','}
fisherdaddy's avatar
fisherdaddy committed
177 178
              </li>
            ))}
179
            ]
fisherdaddy's avatar
fisherdaddy committed
180 181 182 183 184 185 186 187 188 189 190 191 192
          </JsonList>
        )}
      </div>
    );
  } else if (typeof data === 'object' && data !== null) {
    return (
      <div>
        <ToggleButton onClick={() => setIsExpanded(!isExpanded)}>
          {isExpanded ? '{-}' : '{+}'}
        </ToggleButton>
        {!isExpanded && <span>Object</span>}
        {isExpanded && (
          <JsonList>
193 194
            {'{'}
            {Object.entries(data).map(([key, value], index, array) => (
fisherdaddy's avatar
fisherdaddy committed
195 196
              <li key={key}>
                <Key>"{key}"</Key>: <JsonView data={value} />
197
                {index < array.length - 1 && ','}
fisherdaddy's avatar
fisherdaddy committed
198 199
              </li>
            ))}
200
            {'}'}
fisherdaddy's avatar
fisherdaddy committed
201 202 203 204 205 206 207 208 209 210 211 212
          </JsonList>
        )}
      </div>
    );
  } else if (typeof data === 'string') {
    return <Value>"{data}"</Value>;
  } else {
    return <Value>{JSON.stringify(data)}</Value>;
  }
}

export default JsonFormatter;