2021-04-29 07:58:40 +00:00
|
|
|
|
import { useState, useCallback } from 'react'
|
2021-01-21 15:56:49 +00:00
|
|
|
|
import './App.css'
|
2021-01-21 21:12:15 +00:00
|
|
|
|
import { useDropzone } from 'react-dropzone'
|
2021-01-23 18:38:20 +00:00
|
|
|
|
import mergeImages from 'merge-images'
|
2021-01-21 18:11:37 +00:00
|
|
|
|
import FrameChooser from './FrameChooser.js'
|
2021-01-23 17:57:34 +00:00
|
|
|
|
import HashtagChooser from './HashtagChooser.js'
|
2021-01-23 11:50:39 +00:00
|
|
|
|
import Editor from './Editor.js'
|
2021-01-23 21:09:14 +00:00
|
|
|
|
import VoltLogoPurple from './VoltLogoPurple.svg'
|
2021-01-23 17:53:46 +00:00
|
|
|
|
import purpleBG from './purpleBG.png'
|
2021-01-23 17:58:30 +00:00
|
|
|
|
import empty_1x1 from './empty_1x1.png'
|
2021-01-21 15:56:49 +00:00
|
|
|
|
|
2021-01-23 19:14:41 +00:00
|
|
|
|
import { withLocalization, Localized } from './Localized.js'
|
2021-01-23 18:39:01 +00:00
|
|
|
|
|
2021-01-21 15:56:49 +00:00
|
|
|
|
const frameSize = 1080
|
2021-01-21 14:18:25 +00:00
|
|
|
|
|
2021-01-22 07:40:31 +00:00
|
|
|
|
function getOrientation(file, callback) {
|
|
|
|
|
// Source: http://stackoverflow.com/a/32490603
|
2021-01-22 07:45:09 +00:00
|
|
|
|
// (With some modifications: I just made the code fit the style-guide.)
|
|
|
|
|
const reader = new FileReader()
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
|
|
|
|
reader.onload = function (event) {
|
2021-01-22 07:45:09 +00:00
|
|
|
|
const view = new DataView(event.target.result)
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-22 07:45:09 +00:00
|
|
|
|
if (view.getUint16(0, false) !== 0xFFD8) {
|
|
|
|
|
return callback(-2)
|
|
|
|
|
}
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-22 07:45:09 +00:00
|
|
|
|
const length = view.byteLength
|
|
|
|
|
let offset = 2
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
|
|
|
|
while (offset < length) {
|
2021-01-22 07:45:09 +00:00
|
|
|
|
const marker = view.getUint16(offset, false)
|
|
|
|
|
offset += 2
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
|
|
|
|
if (marker === 0xFFE1) {
|
|
|
|
|
if (view.getUint32(offset += 2, false) !== 0x45786966) {
|
2021-01-22 07:45:09 +00:00
|
|
|
|
return callback(-1)
|
2021-01-22 07:40:31 +00:00
|
|
|
|
}
|
2021-01-22 07:45:09 +00:00
|
|
|
|
const little = view.getUint16(offset += 6, false) === 0x4949
|
|
|
|
|
offset += view.getUint32(offset + 4, little)
|
|
|
|
|
const tags = view.getUint16(offset, little)
|
|
|
|
|
offset += 2
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-22 07:45:09 +00:00
|
|
|
|
for (var i = 0; i < tags; i++) {
|
|
|
|
|
if (view.getUint16(offset + (i * 12), little) === 0x0112) {
|
|
|
|
|
return callback(view.getUint16(offset + (i * 12) + 8, little))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if ((marker & 0xFF00) !== 0xFF00) {
|
|
|
|
|
break
|
|
|
|
|
} else {
|
|
|
|
|
offset += view.getUint16(offset, false)
|
2021-01-22 07:40:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-22 07:45:09 +00:00
|
|
|
|
return callback(-1)
|
|
|
|
|
}
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-22 07:45:09 +00:00
|
|
|
|
reader.readAsArrayBuffer(file.slice(0, 64 * 1024))
|
|
|
|
|
}
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-23 11:50:39 +00:00
|
|
|
|
function trigger_download(name, data){
|
|
|
|
|
const a = document.createElement('a')
|
|
|
|
|
document.body.appendChild(a)
|
2021-01-23 12:47:32 +00:00
|
|
|
|
// a.target = '_blank'
|
2021-01-23 11:50:39 +00:00
|
|
|
|
a.download = name
|
2021-01-23 12:15:55 +00:00
|
|
|
|
a.href = data
|
2021-01-23 11:50:39 +00:00
|
|
|
|
a.click()
|
2021-01-23 12:47:32 +00:00
|
|
|
|
a.remove()
|
2021-01-23 11:50:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-20 23:36:11 +00:00
|
|
|
|
function UmamiLink({ href, name, target, children, ...props }) {
|
|
|
|
|
const handleClick = useCallback(event => {
|
|
|
|
|
|
2021-09-17 17:03:44 +00:00
|
|
|
|
if (window.umami && name) {
|
2023-05-18 11:49:26 +00:00
|
|
|
|
window.umami.track('A: ' + name) // Log Anker / Link
|
2021-04-20 23:36:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// follow link
|
|
|
|
|
if (!(!!target)) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
window.location = href
|
|
|
|
|
}, 200)
|
|
|
|
|
}else{
|
|
|
|
|
window.open(href, target)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prevent normal href-follow
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
return false
|
2021-04-20 23:40:26 +00:00
|
|
|
|
}, [href, name, target])
|
2021-04-20 23:36:11 +00:00
|
|
|
|
|
|
|
|
|
return <a
|
|
|
|
|
{...props}
|
|
|
|
|
href={href}
|
|
|
|
|
onClick={handleClick}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</a>
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:50:40 +00:00
|
|
|
|
function App({ getString, locales, currentLocale, onLanguageChange }) {
|
2021-04-20 22:00:34 +00:00
|
|
|
|
const [frame, setFrame] = useState(null)
|
|
|
|
|
const [hashtag, setHashtag] = useState(null)
|
2021-01-21 19:54:45 +00:00
|
|
|
|
const [originalPhoto, setOriginalPhoto] = useState(null)
|
2021-01-23 11:50:39 +00:00
|
|
|
|
const [originalPhotoRation, setOriginalPhotoRation] = useState(1)
|
|
|
|
|
const [orientation, set_orientation] = useState(null)
|
|
|
|
|
|
2021-04-20 22:00:34 +00:00
|
|
|
|
const frameURL = !!frame ? frame.src : null
|
|
|
|
|
const hashtagURL = !!hashtag ? hashtag.src : null
|
2021-01-23 11:50:39 +00:00
|
|
|
|
|
|
|
|
|
// const [combinedImage, set_combinedImage] = useState(null)
|
|
|
|
|
|
|
|
|
|
const [width, set_width] = useState(0)
|
|
|
|
|
const [height, set_height] = useState(0)
|
|
|
|
|
|
|
|
|
|
const [cords, setCords] = useState({x:0, y:0, scale:1})
|
2021-01-21 15:56:49 +00:00
|
|
|
|
|
2021-04-20 22:00:34 +00:00
|
|
|
|
const handleFrame = useCallback(newFrame => {
|
|
|
|
|
setFrame(newFrame)
|
|
|
|
|
}, [setFrame])
|
2021-01-21 18:11:37 +00:00
|
|
|
|
|
2021-04-20 22:00:34 +00:00
|
|
|
|
const handleHashtag = useCallback(newHashtag => {
|
|
|
|
|
setHashtag(newHashtag)
|
|
|
|
|
}, [setHashtag])
|
2021-01-23 17:57:34 +00:00
|
|
|
|
|
2021-01-23 11:50:39 +00:00
|
|
|
|
const handleCordsChange = useCallback(({x, y, scale}) => {
|
|
|
|
|
setCords({ x, y, scale })
|
|
|
|
|
}, [])
|
2021-01-21 21:12:15 +00:00
|
|
|
|
|
|
|
|
|
const handleReadFile = useCallback(file => {
|
2021-01-21 21:41:14 +00:00
|
|
|
|
if (!(!!file)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 15:56:49 +00:00
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
reader.onload = reader_event => {
|
|
|
|
|
const img = new Image()
|
|
|
|
|
img.onload = function () {
|
2021-01-21 19:57:03 +00:00
|
|
|
|
let width, height;
|
|
|
|
|
if (img.width < img.height) {
|
|
|
|
|
height = (img.height / img.width) * frameSize
|
|
|
|
|
width = frameSize
|
|
|
|
|
} else {
|
|
|
|
|
height = frameSize
|
|
|
|
|
width = (img.width / img.height) * frameSize
|
|
|
|
|
}
|
2021-01-22 07:40:31 +00:00
|
|
|
|
|
2021-01-23 11:50:39 +00:00
|
|
|
|
getOrientation(file, new_orientation => {
|
|
|
|
|
let original_ration = 1
|
2021-01-22 07:40:31 +00:00
|
|
|
|
// use the correct image orientation
|
2021-01-23 11:50:39 +00:00
|
|
|
|
switch (new_orientation) {
|
2021-01-22 07:40:31 +00:00
|
|
|
|
// Source: https://stackoverflow.com/a/30242954/2387277
|
|
|
|
|
// Source: https://stackoverflow.com/questions/19463126/how-to-draw-photo-with-correct-orientation-in-canvas-after-capture-photo-by-usin
|
|
|
|
|
case 2:
|
|
|
|
|
// horizontal flip
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = height / width
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 3:
|
|
|
|
|
// 180° rotate left
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = height / width
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 4:
|
|
|
|
|
// vertical flip
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = height / width
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 5:
|
|
|
|
|
// vertical flip + 90 rotate right
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = width / height
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 6:
|
|
|
|
|
// 90° rotate right
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = width / height
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 7:
|
|
|
|
|
// horizontal flip + 90 rotate right
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = width / height
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
case 8:
|
|
|
|
|
// 90° rotate left
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = width / height
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
default:
|
2021-01-23 11:50:39 +00:00
|
|
|
|
original_ration = height / width
|
2021-01-22 07:40:31 +00:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-23 11:50:39 +00:00
|
|
|
|
set_width(width)
|
|
|
|
|
set_height(height)
|
|
|
|
|
setOriginalPhoto(reader_event.target.result)
|
|
|
|
|
set_orientation(new_orientation)
|
|
|
|
|
setOriginalPhotoRation(original_ration)
|
2021-01-22 07:40:31 +00:00
|
|
|
|
})
|
2021-01-21 15:56:49 +00:00
|
|
|
|
}
|
|
|
|
|
img.src = reader_event.target.result
|
|
|
|
|
}
|
2021-01-21 21:12:15 +00:00
|
|
|
|
reader.readAsDataURL(file)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const handleImage = useCallback(files_event => {
|
|
|
|
|
handleReadFile(files_event.target.files[0])
|
|
|
|
|
}, [handleReadFile])
|
|
|
|
|
|
|
|
|
|
const onDrop = useCallback(acceptedFiles => {
|
|
|
|
|
handleReadFile(acceptedFiles[0])
|
|
|
|
|
}, [handleReadFile])
|
|
|
|
|
|
2021-01-23 11:50:39 +00:00
|
|
|
|
const handleDownload = useCallback(() => {
|
|
|
|
|
const img = new Image()
|
|
|
|
|
img.onload = function () {
|
|
|
|
|
const canvas = document.createElement('canvas')
|
|
|
|
|
canvas.width = frameSize
|
|
|
|
|
canvas.height = frameSize
|
|
|
|
|
|
|
|
|
|
const ctx = canvas.getContext('2d', { alpha: true })
|
|
|
|
|
|
|
|
|
|
// use the correct image orientation
|
|
|
|
|
switch (orientation) {
|
|
|
|
|
// Source: https://stackoverflow.com/a/30242954/2387277
|
|
|
|
|
// Source: https://stackoverflow.com/questions/19463126/how-to-draw-photo-with-correct-orientation-in-canvas-after-capture-photo-by-usin
|
|
|
|
|
case 2:
|
|
|
|
|
// horizontal flip
|
|
|
|
|
ctx.translate(canvas.width, 0)
|
|
|
|
|
ctx.scale(-1, 1)
|
|
|
|
|
break
|
|
|
|
|
case 3:
|
|
|
|
|
// 180° rotate left
|
|
|
|
|
ctx.translate(canvas.width, canvas.height)
|
|
|
|
|
ctx.rotate(Math.PI)
|
|
|
|
|
break
|
|
|
|
|
case 4:
|
|
|
|
|
// vertical flip
|
|
|
|
|
ctx.translate(0, canvas.height)
|
|
|
|
|
ctx.scale(1, -1)
|
|
|
|
|
break
|
|
|
|
|
case 5:
|
|
|
|
|
// vertical flip + 90 rotate right
|
|
|
|
|
ctx.rotate(0.5 * Math.PI)
|
|
|
|
|
ctx.scale(1, -1)
|
|
|
|
|
break
|
|
|
|
|
case 6:
|
|
|
|
|
// 90° rotate right
|
|
|
|
|
ctx.rotate(0.5 * Math.PI)
|
|
|
|
|
ctx.translate(0, -canvas.height)
|
|
|
|
|
break
|
|
|
|
|
case 7:
|
|
|
|
|
// horizontal flip + 90 rotate right
|
|
|
|
|
ctx.rotate(0.5 * Math.PI)
|
|
|
|
|
ctx.translate(canvas.width, -canvas.height)
|
|
|
|
|
ctx.scale(-1, 1)
|
|
|
|
|
break
|
|
|
|
|
case 8:
|
|
|
|
|
// 90° rotate left
|
|
|
|
|
ctx.rotate(-0.5 * Math.PI)
|
|
|
|
|
ctx.translate(-canvas.width, 0)
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const width_scaled = width * cords.scale
|
|
|
|
|
const height_scaled = height * cords.scale
|
|
|
|
|
|
|
|
|
|
ctx.drawImage(
|
|
|
|
|
img,
|
|
|
|
|
cords.x * 3.5 + (frameSize - width_scaled) * 0.5,
|
|
|
|
|
cords.y * 3.5 + (frameSize - height_scaled) * 0.5,
|
|
|
|
|
width_scaled,
|
|
|
|
|
height_scaled,
|
|
|
|
|
)
|
|
|
|
|
// ctx.drawImage(
|
|
|
|
|
// img,
|
|
|
|
|
// ((frameSize - width_scaled) * 0.5),
|
|
|
|
|
// ((frameSize - height_scaled) * 0.5),
|
|
|
|
|
// width_scaled,
|
|
|
|
|
// height_scaled,
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
const pngUrl = canvas.toDataURL()
|
|
|
|
|
|
|
|
|
|
mergeImages([
|
2021-01-23 17:53:46 +00:00
|
|
|
|
purpleBG,
|
2021-01-23 11:50:39 +00:00
|
|
|
|
...(pngUrl ? [pngUrl] : []),
|
|
|
|
|
...(frameURL ? [frameURL] : []),
|
2021-01-23 17:57:34 +00:00
|
|
|
|
...(hashtagURL ? [hashtagURL] : []),
|
2021-01-23 11:50:39 +00:00
|
|
|
|
])
|
|
|
|
|
.then(b64 => {
|
|
|
|
|
// set_combinedImage(b64)
|
|
|
|
|
trigger_download('volt-profile-picture.png', b64)
|
2021-04-20 22:57:24 +00:00
|
|
|
|
|
|
|
|
|
const frameName = frame.name || 'No-Frame'
|
|
|
|
|
const hashtagName = hashtag.name || 'No-Hashtag'
|
|
|
|
|
|
2021-09-17 17:03:44 +00:00
|
|
|
|
if (window.umami) {
|
2023-05-18 11:49:26 +00:00
|
|
|
|
window.umami.track('F: ' + frameName) // Log Frame
|
|
|
|
|
window.umami.track('H: ' + hashtagName) // Log Hashtag
|
|
|
|
|
window.umami.track('C: ' + [frameName, hashtagName].join(' | ')) // Log Combined
|
2021-09-17 17:03:44 +00:00
|
|
|
|
}
|
2021-01-23 11:50:39 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
img.src = originalPhoto
|
|
|
|
|
}, [
|
|
|
|
|
originalPhoto,
|
|
|
|
|
cords.x,
|
|
|
|
|
cords.y,
|
|
|
|
|
cords.scale,
|
|
|
|
|
orientation,
|
|
|
|
|
frameURL,
|
2021-01-23 17:57:34 +00:00
|
|
|
|
hashtagURL,
|
2021-01-23 11:50:39 +00:00
|
|
|
|
height,
|
|
|
|
|
width,
|
2021-04-20 22:57:24 +00:00
|
|
|
|
frame,
|
|
|
|
|
hashtag
|
2021-01-23 11:50:39 +00:00
|
|
|
|
])
|
|
|
|
|
|
2021-01-21 21:12:15 +00:00
|
|
|
|
const { isDragActive, getRootProps } = useDropzone({
|
|
|
|
|
onDrop,
|
|
|
|
|
accept: 'image/*',
|
|
|
|
|
maxFiles: 1,
|
|
|
|
|
noKeyboard: true,
|
|
|
|
|
})
|
|
|
|
|
|
2021-01-21 21:13:04 +00:00
|
|
|
|
return (
|
|
|
|
|
<div className="App" {...getRootProps()}>
|
2021-01-23 21:09:14 +00:00
|
|
|
|
<img src={VoltLogoPurple} className="HeaderImage" alt={getString('alt_volt_logo')} />
|
|
|
|
|
<h1><Localized id="title_profile_generator" /></h1>
|
2021-01-21 21:15:01 +00:00
|
|
|
|
|
2021-01-21 21:13:04 +00:00
|
|
|
|
<div className={isDragActive ? 'droparea active' : 'droparea'}>
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<Localized id="title_drop_photo_here" />
|
2021-01-21 21:13:04 +00:00
|
|
|
|
</div>
|
2021-01-21 21:15:01 +00:00
|
|
|
|
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<h2><Localized id="title_choose_photo" /></h2>
|
2021-01-23 20:37:50 +00:00
|
|
|
|
<p><Localized id="text_choose_photo_info" /></p>
|
2021-01-21 21:15:01 +00:00
|
|
|
|
|
2021-01-21 21:13:04 +00:00
|
|
|
|
<label className="labelButton" tabIndex="0" style={{outline:'none'}}>
|
2021-01-23 11:50:39 +00:00
|
|
|
|
{!!originalPhoto ? <img src={originalPhoto} alt="Preview" /> : null}
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<span>{!!originalPhoto ? getString('button_change_photo') : getString('button_load_photo') }</span>
|
2021-01-21 21:13:04 +00:00
|
|
|
|
<input onChange={handleImage} type="file" accept="image/*" style={{display: 'none'}} />
|
|
|
|
|
</label>
|
2021-01-21 21:15:01 +00:00
|
|
|
|
|
2021-01-23 20:33:54 +00:00
|
|
|
|
{true || !!originalPhoto ? (<>
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<h2><Localized id="title_choose_frame" /></h2>
|
2021-04-20 22:00:34 +00:00
|
|
|
|
<FrameChooser onChange={handleFrame} />
|
2021-09-17 16:55:27 +00:00
|
|
|
|
{
|
|
|
|
|
(frameURL || '').startsWith('/static/media/btw_')
|
|
|
|
|
? null
|
|
|
|
|
: <>
|
|
|
|
|
<h2><Localized id="title_choose_hashtag" /></h2>
|
|
|
|
|
<HashtagChooser onChange={handleHashtag} />
|
|
|
|
|
</>
|
|
|
|
|
}
|
2021-01-23 11:50:39 +00:00
|
|
|
|
</>) : null}
|
|
|
|
|
|
2021-04-20 22:01:05 +00:00
|
|
|
|
{!!originalPhoto && !!frameURL ? (<>
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<h2><Localized id="title_reposition_photo" /></h2>
|
2021-01-23 12:50:24 +00:00
|
|
|
|
{/*
|
2021-01-23 11:50:39 +00:00
|
|
|
|
<h2>Edit your Photo:</h2>
|
|
|
|
|
<p>Your can reposition the image and scale it. Use pinch-to-zoom or scroll to scale.</p>
|
2021-01-23 12:50:24 +00:00
|
|
|
|
*/}
|
2021-01-23 11:50:39 +00:00
|
|
|
|
|
|
|
|
|
<Editor
|
2021-01-23 17:53:00 +00:00
|
|
|
|
backgroundURL={originalPhoto || empty_1x1}
|
2021-01-23 11:50:39 +00:00
|
|
|
|
backgroundRatio={originalPhotoRation}
|
2021-01-23 17:53:00 +00:00
|
|
|
|
frameURL={frameURL}
|
|
|
|
|
hashtagURL={hashtagURL || empty_1x1}
|
2021-01-23 11:50:39 +00:00
|
|
|
|
onChange={handleCordsChange}
|
|
|
|
|
/>
|
2021-01-21 21:15:01 +00:00
|
|
|
|
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<button onClick={handleDownload}><Localized id="button_download" /></button>
|
2021-01-23 11:50:39 +00:00
|
|
|
|
</>) : null}
|
2021-01-23 18:20:21 +00:00
|
|
|
|
|
|
|
|
|
<footer>
|
2021-04-20 23:36:11 +00:00
|
|
|
|
<UmamiLink name="imprint" href="https://www.voltdeutschland.org/impressum">
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<Localized id="link_imprint" />
|
2021-04-20 23:36:11 +00:00
|
|
|
|
</UmamiLink>
|
2021-01-23 18:20:21 +00:00
|
|
|
|
•
|
2021-04-20 23:36:11 +00:00
|
|
|
|
<UmamiLink name="privacy_policy" href="https://www.voltdeutschland.org/datenschutz">
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<Localized id="link_privacy_policy" />
|
2021-04-20 23:36:11 +00:00
|
|
|
|
</UmamiLink>
|
2021-01-23 18:20:21 +00:00
|
|
|
|
•
|
2021-04-20 23:36:11 +00:00
|
|
|
|
<UmamiLink name="source_code" href="https://github.com/voltbonn/profile-picture-generator">
|
2021-01-23 19:14:41 +00:00
|
|
|
|
<Localized id="link_source_code" />
|
2021-04-20 23:36:11 +00:00
|
|
|
|
</UmamiLink>
|
2021-04-20 21:19:11 +00:00
|
|
|
|
•
|
2021-04-20 23:36:11 +00:00
|
|
|
|
<UmamiLink name="contact" href="mailto:thomas.rosen@volteuropa.org">
|
2021-04-20 21:19:11 +00:00
|
|
|
|
<Localized id="link_app_contact" />
|
2021-04-20 23:36:11 +00:00
|
|
|
|
</UmamiLink>
|
2021-01-23 18:20:21 +00:00
|
|
|
|
</footer>
|
2021-04-29 07:50:40 +00:00
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
!!locales && !!onLanguageChange
|
|
|
|
|
? <div className="locale_chooser">
|
|
|
|
|
{
|
|
|
|
|
Object.entries(locales)
|
|
|
|
|
.map(([locale, name]) => {
|
|
|
|
|
return <button
|
|
|
|
|
className={locale === currentLocale ? 'isInRow choosen' : 'isInRow'}
|
|
|
|
|
key={locale}
|
|
|
|
|
data-locale={locale}
|
|
|
|
|
onClick={onLanguageChange}
|
|
|
|
|
>
|
|
|
|
|
{name}
|
|
|
|
|
</button>
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
: null
|
|
|
|
|
}
|
2021-01-21 21:12:15 +00:00
|
|
|
|
</div>
|
2021-01-21 21:13:04 +00:00
|
|
|
|
)
|
2021-01-21 14:18:25 +00:00
|
|
|
|
}
|
2021-04-29 07:58:40 +00:00
|
|
|
|
export default withLocalization(App)
|