UrlEnDecode.jsx 4.58 KB
Newer Older
fisherdaddy's avatar
fisherdaddy committed
1 2 3 4 5 6
import React, { useState, useCallback } from 'react';
import { Title, Wrapper, Container, InputText, Preview } from '../js/SharedStyles';
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 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
  flex-direction: column;
`;

const StyledInputText = styled(InputText)`
  height: 100px;
  margin-bottom: 20px;
  @media (min-width: 768px) {
    height: 100px;
    width: 100%;
  }
`;

const PreviewWrapper = styled.div`
  width: 100%;
`;

const Label = styled.label`
  font-weight: 500;
  font-size: 14px;
  color: #5f6368;
  margin-bottom: 8px;
  display: block;
  letter-spacing: 0.1px;
`;

const StyledPreview = styled(Preview)`
  background-color: #f8f9fa;
  padding: 12px 40px 12px 12px; // 增加右侧 padding 为按钮留出空间
  border-radius: 8px;
  border: 1px solid #dadce0;
  font-size: 14px;
  color: #202124;
  min-height: 24px; // 确保即使内容为空,也有足够的高度容纳按钮
`;

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

const CopyButton = styled.button`
  position: absolute;
  top: 8px;
  right: 8px;
  background-color: transparent;
  border: none;
  cursor: pointer;
  padding: 4px;
  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 {
fisherdaddy's avatar
fisherdaddy committed
72
    color: #34a853; // 成功复制后的反馈颜色
fisherdaddy's avatar
fisherdaddy committed
73 74 75
  }
`;

fisherdaddy's avatar
fisherdaddy committed
76 77 78 79 80 81 82 83 84 85 86 87 88
const ModeSwitcher = styled.div`
  margin-bottom: 20px;

  select {
    padding: 8px;
    border-radius: 4px;
    border: 1px solid #dadce0;
    font-size: 14px;
    color: #202124;
  }
`;

function UrlEncoderDecoder() {
fisherdaddy's avatar
fisherdaddy committed
89 90
  const { t } = useTranslation();
  const [input, setInput] = useState('');
fisherdaddy's avatar
fisherdaddy committed
91
  const [resultText, setResultText] = useState('');
fisherdaddy's avatar
fisherdaddy committed
92
  const [isCopied, setIsCopied] = useState(false);
fisherdaddy's avatar
fisherdaddy committed
93 94 95 96 97 98 99 100
  const [mode, setMode] = useState('decode'); // 'encode' 或 'decode'

  const handleModeChange = (e) => {
    setMode(e.target.value);
    // 当模式切换时,清空输入和输出
    setInput('');
    setResultText('');
  };
fisherdaddy's avatar
fisherdaddy committed
101 102 103 104 105

  const handleInputChange = (e) => {
    const inputValue = e.target.value;
    setInput(inputValue);
    try {
fisherdaddy's avatar
fisherdaddy committed
106 107 108 109 110 111 112
      let result;
      if (mode === 'decode') {
        result = decodeURIComponent(inputValue);
      } else {
        result = encodeURIComponent(inputValue);
      }
      setResultText(result);
fisherdaddy's avatar
fisherdaddy committed
113
    } catch (error) {
fisherdaddy's avatar
fisherdaddy committed
114
      setResultText('Invalid input');
fisherdaddy's avatar
fisherdaddy committed
115 116 117 118
    }
  };

  const handleCopy = useCallback(() => {
fisherdaddy's avatar
fisherdaddy committed
119
    navigator.clipboard.writeText(resultText).then(() => {
fisherdaddy's avatar
fisherdaddy committed
120 121 122
      setIsCopied(true);
      setTimeout(() => setIsCopied(false), 2000);
    });
fisherdaddy's avatar
fisherdaddy committed
123
  }, [resultText]);
fisherdaddy's avatar
fisherdaddy committed
124 125 126 127

  return (
    <>
      <SEO
fisherdaddy's avatar
fisherdaddy committed
128 129
        title={t('tools.urlEncodeDecode.title')}
        description={t('tools.urlEncodeDecode.description')}
fisherdaddy's avatar
fisherdaddy committed
130 131
      />
      <Wrapper>
fisherdaddy's avatar
fisherdaddy committed
132 133 134 135 136 137 138 139 140
        <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
141 142
          <StyledInputText
            id="urlInput"
fisherdaddy's avatar
fisherdaddy committed
143
            placeholder={mode === 'decode' ? t('tools.urlDecode.inputLabel') : t('tools.urlEncode.inputLabel')}
fisherdaddy's avatar
fisherdaddy committed
144 145 146 147
            value={input}
            onChange={handleInputChange}
          />
          <PreviewWrapper>
fisherdaddy's avatar
fisherdaddy committed
148
            <Label>{mode === 'decode' ? t('tools.urlDecode.resultLabel') : t('tools.urlEncode.resultLabel')}</Label>
fisherdaddy's avatar
fisherdaddy committed
149
            <ResultContainer>
fisherdaddy's avatar
fisherdaddy committed
150
              <StyledPreview>{resultText}</StyledPreview>
fisherdaddy's avatar
fisherdaddy committed
151 152 153 154 155 156 157 158 159 160 161 162 163
              <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>
            </ResultContainer>
          </PreviewWrapper>
fisherdaddy's avatar
fisherdaddy committed
164
        </EncoderDecoderContainer>
fisherdaddy's avatar
fisherdaddy committed
165 166 167 168 169
      </Wrapper>
    </>
  );
}

fisherdaddy's avatar
fisherdaddy committed
170
export default UrlEncoderDecoder;