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

fisherdaddy's avatar
fisherdaddy committed
7
const EncoderDecoderContainer = styled(Container)`
fisherdaddy's avatar
fisherdaddy committed
8
  flex-direction: column;
fisherdaddy's avatar
fisherdaddy committed
9
  gap: 16px;
fisherdaddy's avatar
fisherdaddy committed
10 11
`;

fisherdaddy's avatar
fisherdaddy committed
12
const StyledInputText = styled.textarea`
fisherdaddy's avatar
fisherdaddy committed
13
  width: 100%;
fisherdaddy's avatar
fisherdaddy committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
  height: 120px;
  font-size: 15px;
  padding: 16px;
  border: 1px solid rgba(99, 102, 241, 0.1);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(10px);
  box-sizing: border-box;
  outline: none;
  resize: none;
  transition: all 0.3s ease;
  line-height: 1.5;

  &:focus {
    border-color: rgba(99, 102, 241, 0.3);
    box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
  }
fisherdaddy's avatar
fisherdaddy committed
31 32 33 34 35
`;

const Label = styled.label`
  font-weight: 500;
  font-size: 14px;
fisherdaddy's avatar
fisherdaddy committed
36
  color: #374151;
fisherdaddy's avatar
fisherdaddy committed
37 38 39 40 41
  margin-bottom: 8px;
  display: block;
  letter-spacing: 0.1px;
`;

fisherdaddy's avatar
fisherdaddy committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
const ModeSwitcher = styled.div`
  margin-bottom: 8px;

  select {
    padding: 8px 12px;
    border-radius: 8px;
    border: 1px solid rgba(99, 102, 241, 0.1);
    font-size: 14px;
    color: #374151;
    background: rgba(255, 255, 255, 0.8);
    backdrop-filter: blur(10px);
    cursor: pointer;
    transition: all 0.3s ease;

    &:focus {
      border-color: rgba(99, 102, 241, 0.3);
      box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
      outline: none;
    }
  }
fisherdaddy's avatar
fisherdaddy committed
62 63 64 65 66 67 68
`;

const ResultContainer = styled.div`
  position: relative;
  width: 100%;
`;

fisherdaddy's avatar
fisherdaddy committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82
const StyledPreview = styled.div`
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(10px);
  border-radius: 12px;
  border: 1px solid rgba(99, 102, 241, 0.1);
  padding: 16px;
  font-size: 15px;
  color: #374151;
  min-height: 24px;
  line-height: 1.5;
  position: relative;
`;

const ActionButton = styled.button`
fisherdaddy's avatar
fisherdaddy committed
83
  position: absolute;
fisherdaddy's avatar
fisherdaddy committed
84 85 86
  top: 12px;
  right: 12px;
  background: rgba(99, 102, 241, 0.1);
fisherdaddy's avatar
fisherdaddy committed
87
  border: none;
fisherdaddy's avatar
fisherdaddy committed
88 89
  border-radius: 6px;
  padding: 6px 12px;
fisherdaddy's avatar
fisherdaddy committed
90 91 92
  cursor: pointer;
  display: flex;
  align-items: center;
fisherdaddy's avatar
fisherdaddy committed
93 94 95 96
  gap: 6px;
  font-size: 13px;
  color: #6366F1;
  transition: all 0.3s ease;
fisherdaddy's avatar
fisherdaddy committed
97 98

  &:hover {
fisherdaddy's avatar
fisherdaddy committed
99
    background: rgba(99, 102, 241, 0.2);
fisherdaddy's avatar
fisherdaddy committed
100 101
  }

fisherdaddy's avatar
fisherdaddy committed
102 103 104
  &.active {
    background: #6366F1;
    color: white;
fisherdaddy's avatar
fisherdaddy committed
105
  }
fisherdaddy's avatar
fisherdaddy committed
106

fisherdaddy's avatar
fisherdaddy committed
107 108 109
  svg {
    width: 14px;
    height: 14px;
fisherdaddy's avatar
fisherdaddy committed
110 111 112 113
  }
`;

function UrlEncoderDecoder() {
fisherdaddy's avatar
fisherdaddy committed
114 115
  const { t } = useTranslation();
  const [input, setInput] = useState('');
fisherdaddy's avatar
fisherdaddy committed
116
  const [resultText, setResultText] = useState('');
fisherdaddy's avatar
fisherdaddy committed
117
  const [isCopied, setIsCopied] = useState(false);
fisherdaddy's avatar
fisherdaddy committed
118 119 120 121 122 123 124 125
  const [mode, setMode] = useState('decode'); // 'encode' 或 'decode'

  const handleModeChange = (e) => {
    setMode(e.target.value);
    // 当模式切换时,清空输入和输出
    setInput('');
    setResultText('');
  };
fisherdaddy's avatar
fisherdaddy committed
126 127 128 129 130

  const handleInputChange = (e) => {
    const inputValue = e.target.value;
    setInput(inputValue);
    try {
fisherdaddy's avatar
fisherdaddy committed
131 132 133 134 135 136 137
      let result;
      if (mode === 'decode') {
        result = decodeURIComponent(inputValue);
      } else {
        result = encodeURIComponent(inputValue);
      }
      setResultText(result);
fisherdaddy's avatar
fisherdaddy committed
138
    } catch (error) {
fisherdaddy's avatar
fisherdaddy committed
139
      setResultText('Invalid input');
fisherdaddy's avatar
fisherdaddy committed
140 141 142 143
    }
  };

  const handleCopy = useCallback(() => {
fisherdaddy's avatar
fisherdaddy committed
144
    navigator.clipboard.writeText(resultText).then(() => {
fisherdaddy's avatar
fisherdaddy committed
145 146 147
      setIsCopied(true);
      setTimeout(() => setIsCopied(false), 2000);
    });
fisherdaddy's avatar
fisherdaddy committed
148
  }, [resultText]);
fisherdaddy's avatar
fisherdaddy committed
149 150 151 152

  return (
    <>
      <SEO
fisherdaddy's avatar
fisherdaddy committed
153 154
        title={t('tools.urlEncodeDecode.title')}
        description={t('tools.urlEncodeDecode.description')}
fisherdaddy's avatar
fisherdaddy committed
155 156
      />
      <Wrapper>
fisherdaddy's avatar
fisherdaddy committed
157 158 159 160 161 162 163 164 165
        <Title>{t('tools.urlEncodeDecode.title')}</Title>
        <EncoderDecoderContainer>
          <ModeSwitcher>
            <Label>{t('tools.urlEncodeDecode.modeLabel')}</Label>
            <select value={mode} onChange={handleModeChange}>
              <option value="encode">{t('tools.urlEncodeDecode.encode')}</option>
              <option value="decode">{t('tools.urlEncodeDecode.decode')}</option>
            </select>
          </ModeSwitcher>
fisherdaddy's avatar
fisherdaddy committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
          
          <div>
            <Label>
              {mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
            </Label>
            <StyledInputText
              value={input}
              onChange={handleInputChange}
              placeholder={mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
            />
          </div>

          <div>
            <Label>
              {mode === 'decode' ? t('tools.urlDecode.resultLabel') : t('tools.urlEncode.resultLabel')}
            </Label>
fisherdaddy's avatar
fisherdaddy committed
182
            <ResultContainer>
fisherdaddy's avatar
fisherdaddy committed
183
              <StyledPreview>{resultText}</StyledPreview>
fisherdaddy's avatar
fisherdaddy committed
184 185 186 187
              <ActionButton 
                onClick={handleCopy} 
                className={isCopied ? 'active' : ''}
              >
fisherdaddy's avatar
fisherdaddy committed
188
                {isCopied ? (
fisherdaddy's avatar
fisherdaddy committed
189
                  <svg viewBox="0 0 24 24" fill="currentColor">
fisherdaddy's avatar
fisherdaddy committed
190 191 192
                    <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
                  </svg>
                ) : (
fisherdaddy's avatar
fisherdaddy committed
193
                  <svg viewBox="0 0 24 24" fill="currentColor">
fisherdaddy's avatar
fisherdaddy committed
194 195 196
                    <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>
                )}
fisherdaddy's avatar
fisherdaddy committed
197 198
                {isCopied ? t('tools.jsonFormatter.copiedMessage') : t('tools.jsonFormatter.copyButton')}
              </ActionButton>
fisherdaddy's avatar
fisherdaddy committed
199
            </ResultContainer>
fisherdaddy's avatar
fisherdaddy committed
200
          </div>
fisherdaddy's avatar
fisherdaddy committed
201
        </EncoderDecoderContainer>
fisherdaddy's avatar
fisherdaddy committed
202 203 204 205 206
      </Wrapper>
    </>
  );
}

fisherdaddy's avatar
fisherdaddy committed
207
export default UrlEncoderDecoder;