diff --git a/packages/concordia-app/package.json b/packages/concordia-app/package.json
index 54356c7..f911dc0 100644
--- a/packages/concordia-app/package.json
+++ b/packages/concordia-app/package.json
@@ -29,6 +29,7 @@
"@ezerous/eth-identity-provider": "~0.1.2",
"@reduxjs/toolkit": "~1.4.0",
"@welldone-software/why-did-you-render": "~6.0.5",
+ "apexcharts": "^3.26.0",
"concordia-contracts": "~0.1.0",
"concordia-shared": "~0.1.0",
"i18next": "^19.8.3",
@@ -37,6 +38,7 @@
"lodash": "^4.17.20",
"prop-types": "~15.7.2",
"react": "~16.13.1",
+ "react-apexcharts": "^1.3.7",
"react-avatar": "~3.9.7",
"react-copy-to-clipboard": "^5.0.3",
"react-dom": "~16.13.1",
diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json
index d4661ce..f9799bd 100644
--- a/packages/concordia-app/public/locales/en/translation.json
+++ b/packages/concordia-app/public/locales/en/translation.json
@@ -74,6 +74,11 @@
"topic.create.form.subject.field.label": "Topic subject",
"topic.create.form.subject.field.placeholder": "Subject",
"topic.list.row.topic.id": "#{{id}}",
+ "topic.poll.tab.graph.title": "Results",
+ "topic.poll.tab.results.votes.count": "{{totalVotes}} votes casted",
+ "topic.poll.tab.vote.form.button.submit": "Submit",
+ "topic.poll.tab.vote.form.radio.label": "Select one of the options:",
+ "topic.poll.tab.vote.title": "Vote",
"username.selector.error.username.empty.message": "Username is required",
"username.selector.error.username.taken.message": "The username {{username}} is already taken.",
"username.selector.username.field.label": "Username",
diff --git a/packages/concordia-app/src/components/PollView/PollGraph/index.jsx b/packages/concordia-app/src/components/PollView/PollGraph/index.jsx
new file mode 100644
index 0000000..be698a9
--- /dev/null
+++ b/packages/concordia-app/src/components/PollView/PollGraph/index.jsx
@@ -0,0 +1,82 @@
+import React, { useMemo } from 'react';
+import Chart from 'react-apexcharts';
+import { Grid, Header } from 'semantic-ui-react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import { CASTED_OPTION_COLOR, DEFAULT_OPTION_COLOR } from '../../../constants/polls/PollGraph';
+
+const PollGraph = (props) => {
+ const {
+ pollOptions, voteCounts, hasUserVoted, userVoteHash,
+ } = props;
+ const { t } = useTranslation();
+
+ const chartOptions = useMemo(() => ({
+ chart: {
+ id: 'topic-poll',
+ },
+ plotOptions: {
+ bar: {
+ horizontal: true,
+ },
+ },
+ colors: [
+ (value) => {
+ if (hasUserVoted && pollOptions[value.dataPointIndex].hash === userVoteHash) {
+ return CASTED_OPTION_COLOR;
+ }
+ return DEFAULT_OPTION_COLOR;
+ },
+ ],
+ xaxis: {
+ categories: pollOptions.map((pollOption) => pollOption.label),
+ },
+ }), [hasUserVoted, pollOptions, userVoteHash]);
+
+ const chartSeries = useMemo(() => [{
+ name: 'votes',
+ data: voteCounts,
+ }], [voteCounts]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {t('topic.poll.tab.results.votes.count', {
+ totalVotes: voteCounts.reduce((accumulator, voteCount) => accumulator + voteCount, 0),
+ })}
+
+
+
+
+ );
+};
+
+PollGraph.defaultProps = {
+ hasUserVoted: false,
+ userVoteHash: '',
+};
+
+PollGraph.propTypes = {
+ pollOptions: PropTypes.arrayOf(PropTypes.exact({
+ label: PropTypes.string,
+ hash: PropTypes.string,
+ })).isRequired,
+ voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired,
+ hasUserVoted: PropTypes.bool,
+ userVoteHash: PropTypes.string,
+};
+
+export default PollGraph;
diff --git a/packages/concordia-app/src/components/PollView/PollVote/index.jsx b/packages/concordia-app/src/components/PollView/PollVote/index.jsx
new file mode 100644
index 0000000..3b4641e
--- /dev/null
+++ b/packages/concordia-app/src/components/PollView/PollVote/index.jsx
@@ -0,0 +1,62 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Form } from 'semantic-ui-react';
+import { useTranslation } from 'react-i18next';
+
+const PollVote = (props) => {
+ const {
+ pollOptions, enableVoteChanges, hasUserVoted, userVoteHash,
+ } = props;
+ const [selectedOptionHash, setSelectedOptionHash] = useState(userVoteHash);
+ const { t } = useTranslation();
+
+ const onOptionSelected = (e, { value }) => {
+ setSelectedOptionHash(value);
+ };
+
+ const onCastVote = () => {
+ console.log('vote');
+ // TODO
+ // TODO: callback for immediate poll data refresh on vote?
+ };
+
+ return (
+
+
+ {pollOptions.map((pollOption) => (
+
+ ))}
+
+
+ {t('topic.poll.tab.vote.form.button.submit')}
+
+
+ );
+};
+
+PollVote.defaultProps = {
+ userVoteHash: '',
+};
+
+PollVote.propTypes = {
+ pollOptions: PropTypes.arrayOf(PropTypes.exact({
+ label: PropTypes.string,
+ hash: PropTypes.string,
+ })).isRequired,
+ enableVoteChanges: PropTypes.bool.isRequired,
+ hasUserVoted: PropTypes.bool.isRequired,
+ userVoteHash: PropTypes.string,
+};
+
+export default PollVote;
diff --git a/packages/concordia-app/src/components/PollView/index.jsx b/packages/concordia-app/src/components/PollView/index.jsx
new file mode 100644
index 0000000..6c65d05
--- /dev/null
+++ b/packages/concordia-app/src/components/PollView/index.jsx
@@ -0,0 +1,99 @@
+import React, { useMemo, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
+import {
+ Container, Header, Icon, Tab,
+} from 'semantic-ui-react';
+import { useTranslation } from 'react-i18next';
+import { drizzle } from '../../redux/store';
+import PollGraph from './PollGraph';
+import CustomLoadingTabPane from '../CustomLoadingTabPane';
+import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs';
+import PollVote from './PollVote';
+
+const { contracts: { [VOTING_CONTRACT]: { methods: { pollExists: { cacheCall: pollExistsChainData } } } } } = drizzle;
+
+const hashOption = (val) => {
+ let hash = 0;
+ let i;
+ let chr;
+
+ for (i = 0; i < val.length; i++) {
+ chr = val.charCodeAt(i);
+ hash = ((hash << 5) - hash) + chr;
+ hash |= 0;
+ }
+
+ return `${hash}`;
+};
+
+const PollView = (props) => {
+ const { topicId } = props;
+ const userAddress = useSelector((state) => state.user.address);
+ const hasSignedUp = useSelector((state) => state.user.hasSignedUp);
+ const getPollInfoResults = useSelector((state) => state.contracts[VOTING_CONTRACT].getPollInfo);
+ const [getPollInfoCallHash, setGetPollInfoCallHash] = useState(null);
+ const [pollOptions, setPollOptions] = useState([
+ {
+ label: 'option 1',
+ hash: hashOption('option 1'),
+ },
+ {
+ label: 'option 2',
+ hash: hashOption('option 2'),
+ },
+ {
+ label: 'option 3',
+ hash: hashOption('option 3'),
+ },
+ {
+ label: 'option 4',
+ hash: hashOption('option 4'),
+ },
+ {
+ label: 'option 5',
+ hash: hashOption('option 5'),
+ },
+
+ ]);
+ const [voteCounts, setVoteCounts] = useState([2, 8, 4, 12, 7]);
+ const [voteLoading, setVoteLoading] = useState(false);
+ const [resultsLoading, setResultsLoading] = useState(false);
+ const { t } = useTranslation();
+
+ // TODO: get vote options
+
+ // TODO: get current results
+
+ // TODO: check poll hash validity, add invalid view
+
+ const pollVoteTab = useMemo(() => (
+
+ ), [pollOptions]);
+
+ const pollGraphTab = useMemo(() => (
+
+ ), [pollOptions, voteCounts]);
+
+ const panes = useMemo(() => {
+ const pollVotePane = ({pollVoteTab});
+ const pollGraphPane = ({pollGraphTab});
+
+ return ([
+ { menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane },
+ { menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane },
+ ]);
+ }, [pollGraphTab, pollVoteTab, resultsLoading, t, voteLoading]);
+
+ return (
+
+
+
+ Do you thing asdf or fdsa?
+
+
+
+ );
+};
+
+export default PollView;
diff --git a/packages/concordia-app/src/constants/polls/PollGraph.js b/packages/concordia-app/src/constants/polls/PollGraph.js
new file mode 100644
index 0000000..a19a2ee
--- /dev/null
+++ b/packages/concordia-app/src/constants/polls/PollGraph.js
@@ -0,0 +1,2 @@
+export const DEFAULT_OPTION_COLOR = '#3B5066';
+export const CASTED_OPTION_COLOR = '#0b2540';
diff --git a/packages/concordia-app/src/constants/polls/PollTabs.js b/packages/concordia-app/src/constants/polls/PollTabs.js
new file mode 100644
index 0000000..b48a05e
--- /dev/null
+++ b/packages/concordia-app/src/constants/polls/PollTabs.js
@@ -0,0 +1,16 @@
+export const VOTE_TAB = {
+ id: 'vote-tab',
+ intl_display_name_id: 'topic.poll.vote.tab.title',
+};
+
+export const GRAPH_TAB = {
+ id: 'graph-tab',
+ intl_display_name_id: 'topic.poll.graph.tab.title',
+};
+
+const pollTabs = [
+ VOTE_TAB,
+ GRAPH_TAB,
+];
+
+export default pollTabs;
diff --git a/packages/concordia-app/src/views/Topic/TopicView/index.jsx b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
index 602fa54..8e1f6d3 100644
--- a/packages/concordia-app/src/views/Topic/TopicView/index.jsx
+++ b/packages/concordia-app/src/views/Topic/TopicView/index.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import {
@@ -6,19 +6,25 @@ import {
} from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { useHistory } from 'react-router';
-import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
+import { FORUM_CONTRACT, VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import { TOPICS_DATABASE, USER_DATABASE } from 'concordia-shared/src/constants/orbit/OrbitDatabases';
import ReactMarkdown from 'react-markdown';
import { breeze, drizzle } from '../../../redux/store';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import './styles.css';
import TopicPostList from './TopicPostList';
+import PollView from '../../../components/PollView';
import determineKVAddress from '../../../utils/orbitUtils';
import { TOPIC_SUBJECT } from '../../../constants/orbit/TopicsDatabaseKeys';
import PostCreate from '../../../components/PostCreate';
import targetBlank from '../../../utils/markdownUtils';
-const { contracts: { [FORUM_CONTRACT]: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle;
+const {
+ contracts: {
+ [FORUM_CONTRACT]: { methods: { getTopic: { cacheCall: getTopicChainData } } },
+ [VOTING_CONTRACT]: { methods: { pollExists: { cacheCall: pollExistsChainData } } },
+ },
+} = drizzle;
const { orbit } = breeze;
const TopicView = (props) => {
@@ -29,15 +35,18 @@ const TopicView = (props) => {
const userAddress = useSelector((state) => state.user.address);
const hasSignedUp = useSelector((state) => state.user.hasSignedUp);
const getTopicResults = useSelector((state) => state.contracts[FORUM_CONTRACT].getTopic);
+ const pollExistsResults = useSelector((state) => state.contracts[VOTING_CONTRACT].pollExists);
const topics = useSelector((state) => state.orbitData.topics);
const users = useSelector((state) => state.orbitData.users);
- const [getTopicCallHash, setGetTopicCallHash] = useState([]);
+ const [getTopicCallHash, setGetTopicCallHash] = useState(null);
+ const [pollExistsCallHash, setPollExistsCallHash] = useState(null);
const [topicAuthorAddress, setTopicAuthorAddress] = useState(initialTopicAuthorAddress || null);
const [topicAuthor, setTopicAuthor] = useState(initialTopicAuthor || null);
const [timestamp, setTimestamp] = useState(initialTimestamp || null);
const [postIds, setPostIds] = useState(initialPostIds || null);
const [numberOfReplies, setReplyCount] = useState(0);
const [topicSubject, setTopicSubject] = useState(null);
+ const [hasPoll, setHasPoll] = useState(false);
const history = useHistory();
const dispatch = useDispatch();
@@ -52,6 +61,10 @@ const TopicView = (props) => {
}
}, [postIds, timestamp, topicAuthor, topicAuthorAddress, topicId]);
+ useEffect(() => {
+ setPollExistsCallHash(pollExistsChainData(topicId));
+ }, [topicId]);
+
useEffect(() => {
if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) {
if (getTopicResults[getTopicCallHash].value == null) {
@@ -62,9 +75,9 @@ const TopicView = (props) => {
setTopicAuthorAddress(getTopicResults[getTopicCallHash].value[0]);
setTopicAuthor(getTopicResults[getTopicCallHash].value[1]);
setTimestamp(getTopicResults[getTopicCallHash].value[2] * 1000);
- const postIds = getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10));
- setPostIds(postIds);
- setReplyCount(postIds.length - 1);
+ const fetchedPostIds = getTopicResults[getTopicCallHash].value[3].map((postId) => parseInt(postId, 10));
+ setPostIds(fetchedPostIds);
+ setReplyCount(fetchedPostIds.length - 1);
const topicFound = topics
.find((topic) => topic.id === topicId);
@@ -80,6 +93,12 @@ const TopicView = (props) => {
}
}, [dispatch, getTopicCallHash, getTopicResults, history, topicId, topics, userAddress]);
+ useEffect(() => {
+ if (pollExistsCallHash && pollExistsResults && pollExistsResults[pollExistsCallHash]) {
+ setHasPoll(pollExistsResults[pollExistsCallHash].value);
+ }
+ }, [pollExistsCallHash, pollExistsResults, topicId]);
+
useEffect(() => {
if (topicAuthorAddress !== null) {
determineKVAddress({ orbit, dbName: USER_DATABASE, userAddress: topicAuthorAddress })
@@ -111,6 +130,8 @@ const TopicView = (props) => {
}
}, [topicId, topics]);
+ const poll = useMemo(() => hasPoll && , [hasPoll]);
+
const stopClickPropagation = (event) => {
event.stopPropagation();
};
@@ -147,7 +168,9 @@ const TopicView = (props) => {
- { topicAuthor }
+
+ {topicAuthor}
+
@@ -155,6 +178,16 @@ const TopicView = (props) => {
+ {
+ hasPoll && (
+ <>
+
+ {poll}
+
+
+ >
+ )
+ }
diff --git a/yarn.lock b/yarn.lock
index 4dc7c8e..03d6ff4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3084,6 +3084,18 @@ anymatch@~3.1.1:
normalize-path "^3.0.0"
picomatch "^2.0.4"
+apexcharts@^3.26.0:
+ version "3.26.0"
+ resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.26.0.tgz#a78abc108b2e1b3086a738f0ec7c98e292f4a14b"
+ integrity sha512-zdYHs3k3tgmCn1BpYLj7rhGEndBYF33Pq1+g0ora37xAr+3act5CJrpdXM2jx2boVUyXgavoSp6sa8WpK7RkSA==
+ dependencies:
+ svg.draggable.js "^2.2.2"
+ svg.easing.js "^2.0.0"
+ svg.filter.js "^2.0.2"
+ svg.pathmorphing.js "^0.1.3"
+ svg.resize.js "^1.4.3"
+ svg.select.js "^3.0.1"
+
app-module-path@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5"
@@ -14910,7 +14922,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2:
+prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -15185,6 +15197,13 @@ rc@^1.2.7, rc@^1.2.8:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-apexcharts@^1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/react-apexcharts/-/react-apexcharts-1.3.7.tgz#42c8785e260535a4f8db1aadbe7b66552770944d"
+ integrity sha512-2OFhEHd70/WHN0kmrJtVx37UfaL71ZogVkwezmDqwQWgwhK6upuhlnEEX7tEq4xvjA+RFDn6hiUTNIuC/Q7Zqw==
+ dependencies:
+ prop-types "^15.5.7"
+
react-app-polyfill@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0"
@@ -16469,6 +16488,9 @@ snake-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
snapdragon-node@^2.0.1:
version "2.1.1"
@@ -17163,6 +17185,61 @@ svg-parser@^2.0.0:
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
+svg.draggable.js@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba"
+ integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==
+ dependencies:
+ svg.js "^2.0.1"
+
+svg.easing.js@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12"
+ integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=
+ dependencies:
+ svg.js ">=2.3.x"
+
+svg.filter.js@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203"
+ integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=
+ dependencies:
+ svg.js "^2.2.5"
+
+svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d"
+ integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==
+
+svg.pathmorphing.js@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65"
+ integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==
+ dependencies:
+ svg.js "^2.4.0"
+
+svg.resize.js@^1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332"
+ integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==
+ dependencies:
+ svg.js "^2.6.5"
+ svg.select.js "^2.1.2"
+
+svg.select.js@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73"
+ integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==
+ dependencies:
+ svg.js "^2.2.5"
+
+svg.select.js@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917"
+ integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==
+ dependencies:
+ svg.js "^2.6.5"
+
svgo@^1.0.0, svgo@^1.2.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"