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

6 7 8 9 10 11 12 13 14 15
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
16

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

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

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

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;
`;

60 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
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
92
function JsonFormatter() {
93
  const { t } = useTranslation();
fisherdaddy's avatar
fisherdaddy committed
94 95
  const [input, setInput] = useState('');
  const [parsedJson, setParsedJson] = useState(null);
96
  const [isCopied, setIsCopied] = useState(false);
fisherdaddy's avatar
fisherdaddy committed
97 98 99 100 101 102 103 104 105 106

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

107 108 109 110 111 112 113 114 115 116
  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
117
  return (
118 119 120 121 122 123 124 125 126
    <Wrapper>
      <Title>{t('tools.jsonFormatter.title')}</Title>
      <Container>
        <InputText
          placeholder={t('tools.jsonFormatter.inputPlaceholder')}
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
        <RelativePreviewContainer>
fisherdaddy's avatar
fisherdaddy committed
127
          {parsedJson ? (
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
            <>
              <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>
            </>
fisherdaddy's avatar
fisherdaddy committed
144
          ) : (
145
            <Preview>{t('tools.jsonFormatter.invalidJson')}</Preview>
fisherdaddy's avatar
fisherdaddy committed
146
          )}
147 148 149
        </RelativePreviewContainer>
      </Container>
    </Wrapper>
fisherdaddy's avatar
fisherdaddy committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
  );
}

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

export default JsonFormatter;