├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .storybook
├── fsMock.js
└── main.js
├── CONTRIBUTING.md
├── License
├── README.md
├── dist
├── App.css
├── App.js
├── App.test.js
├── assets
│ └── images
│ │ └── computing-cloud.svg
├── components
│ ├── Choose
│ │ ├── Choose.css
│ │ └── index.js
│ ├── InputFile
│ │ ├── InputFile.css
│ │ ├── InputFile.test.js
│ │ └── index.js
│ ├── InputUrl
│ │ ├── InputUrl.css
│ │ ├── InputUrl.test.js
│ │ └── index.js
│ ├── Metadata
│ │ ├── Metadata.css
│ │ ├── Metadata.test.js
│ │ └── index.js
│ ├── ProgressBar
│ │ ├── ProgressBar.css
│ │ └── index.js
│ ├── Switcher
│ │ ├── Switcher.css
│ │ └── index.js
│ ├── TableSchema
│ │ ├── TableSchema.css
│ │ ├── TableSchema.test.js
│ │ └── index.js
│ └── Upload
│ │ ├── Upload.test.js
│ │ └── index.js
├── core.js
├── db
│ ├── encode.json
│ ├── licenses.json
│ ├── resource_formats.json
│ └── types.json
├── index.js
├── setupTests.js
└── utils
│ ├── Utils.test.js
│ └── index.js
├── examples
├── basic
│ ├── README.md
│ ├── assets
│ │ ├── after-css.png
│ │ ├── before-css.png
│ │ ├── table-schema-example.md
│ │ └── upload.png
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── manifest.json
│ │ └── robots.txt
│ └── yarn.lock
└── table-schema
│ ├── README.md
│ ├── assets
│ ├── app.png
│ └── table.png
│ ├── package.json
│ ├── public
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
│ ├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── setupTests.js
│ └── yarn.lock
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── assets
│ └── images
│ │ └── computing-cloud.svg
├── components
│ ├── Choose
│ │ ├── Choose.css
│ │ └── index.jsx
│ ├── InputFile
│ │ ├── InputFile.css
│ │ ├── InputFile.test.js
│ │ └── index.jsx
│ ├── InputUrl
│ │ ├── InputUrl.css
│ │ ├── InputUrl.test.js
│ │ └── index.jsx
│ ├── Metadata
│ │ ├── Metadata.css
│ │ ├── Metadata.test.js
│ │ └── index.jsx
│ ├── ProgressBar
│ │ ├── ProgressBar.css
│ │ └── index.jsx
│ ├── Switcher
│ │ ├── Switcher.css
│ │ └── index.jsx
│ ├── TableSchema
│ │ ├── TableSchema.css
│ │ ├── TableSchema.test.js
│ │ └── index.jsx
│ └── Upload
│ │ ├── Upload.test.js
│ │ └── index.jsx
├── core.js
├── db
│ ├── encode.json
│ ├── licenses.json
│ ├── resource_formats.json
│ └── types.json
├── index.js
├── setupTests.js
└── utils
│ ├── Utils.test.js
│ └── index.js
├── stories
├── 1-app..stories.js
├── 2-uploader.stories.js
├── 3-metadata.stories.js
├── 4-TableSchema.stories.js
├── 5-InputFile.stories.js
├── 6-InputUrl.stories.js
└── 7-Choose.stories.js
└── yarn.lock
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: datapub actions
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x]
20 |
21 | steps:
22 | - name: Checkout repository
23 | uses: actions/checkout@v2
24 |
25 | - name: Set up Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 |
30 | - name: Install dependencies
31 | run: npm install
32 |
33 | - name: Run the tests
34 | run: npm test
35 |
36 | - name: Build
37 | run: CI=false npm run build
38 |
39 | - name: Deploy storybook to Github Pages
40 | run: npm run deploy-storybook -- --ci
41 | env:
42 | GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # IDEs
4 | /.idea
5 |
6 | # dependencies
7 | /node_modules
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # production
15 | /build
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | /storybook-static/*
--------------------------------------------------------------------------------
/.storybook/fsMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | readFileSync: () => 'mocked file',
3 | }
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | // Add fs mockFile because storybook broken without the fs
4 | module.exports = {
5 | stories: ['../stories/**/*.stories.js'],
6 | addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-controls', '@storybook/addon-docs'],
7 | webpackFinal: async (storybookBaseConfig, configType) => {
8 | storybookBaseConfig.resolve.alias = {
9 | ...storybookBaseConfig.resolve.alias,
10 | 'fs': path.resolve(__dirname, 'fsMock.js')
11 | };
12 |
13 | return storybookBaseConfig;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | 1. Fork it!
4 | 2. Create your feature branch: `git checkout -b my-new-feature`
5 | 3. Commit your changes: `git commit -m '[ex][m]: plotly json examples - fixes #23.'`
6 | 4. Push to the branch: `git push origin my-new-feature`
7 |
8 | - After your pull request is merged, you can safely delete your branch.
9 | - More information related version control in [datopian documentation](https://playbook.datopian.com/style-guide/version-control/#commit-messages)
10 |
11 | ### [<-- Back](https://github.com/datopian/datapub)
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Datopian
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Datapub
4 |
5 | [](https://github.com/datopian/datapub/issues)
6 | 
7 | [](http://opensource.org/licenses/MIT)
8 |
9 | DataPub is a React-based framework for rapidly building modern data publishing flows (esp for CKAN). It provides a variety of core components as well as example apps and flows.
10 |
11 |
12 |
13 | ## Background
14 |
15 | This is a brief summary from https://tech.datopian.com/publish/ -- read that for more detail.
16 |
17 | ### What do we mean by data publishing?
18 |
19 | The process of publishing data files and datasets (collections of files).
20 |
21 | Specifically, the process of getting your data files stored and described in a data portal or other platform. Usually it involves steps like:
22 |
23 | * Uploading or linking the files into the platform
24 | * Describing thoese files with high level metadata e.g. the name and description of the dataset or files, their license etc
25 | * Specific metadata about the data e.g. its structure, what fields there are and their types (e.g. integer, string)
26 |
27 | ### Why DataPub?
28 |
29 | At Datopian we have been building data publishing flows for nearly 15 years both in tools like CKAN and OpenSpending and in custom applications. Our experience has taught us two things:
30 |
31 | * Data Publishing flows almost always have some custom aspect. Whether it is a small tweak like adding a specific metadata field or a complex change like add data validation.
32 | * There are many common components e.g. file upload and common patterns to many overall flows e.g. add a file, add metadata, save!
33 |
34 | This indicates the need for a **framework** -- rather than a single one-size-fits-all application.
35 |
36 | ### The DataPub approach
37 |
38 | * **🏗️ React-based**: individual data publishing flows will be React apps that you can boot with standard tools like `create-react-app` and where you can use the full ecosystem of React tooling and components
39 | * **📦 Core components**: provide a suite of tried and tested core components common to many publishing flows such as file upload, table schema editor etc
40 | * **🚂 [Template apps](./examples/)**: provide examples of full-scale apps which developers building new flows can use for inspiration and instruction e.g. copy and paste an example and then modify it
41 |
42 | ## Components
43 |
44 | Components include:
45 |
46 | * [File Upload](https://datopian.github.io/datapub/iframe.html?id=components-upload--idle)
47 | * [File Input](https://datopian.github.io/datapub/iframe.html?id=components-input-file--drag-and-drop)
48 | * [File Input Url](https://datopian.github.io/datapub/iframe.html?id=components-input-url--default)
49 | * [Table Schema](https://datopian.github.io/datapub/iframe.html?id=components-tableschema--idle)
50 | * [Metadata](https://datopian.github.io/datapub/iframe.html?id=components-metadata--idle)
51 | * ProgressBar
52 | * Switcher
53 |
54 |
55 | To see all the available components visit our Storybook:
56 |
57 | https://datopian.github.io/datapub
58 |
59 | ## Example Apps
60 |
61 | See the [`examples`][ex] directory.
62 |
63 | For other full scale apps using DataPub in the wild see:
64 |
65 | * https://github.com/datopian/datapub-nhs
66 |
67 | [ex]: ./examples/
68 |
69 | ## Getting started
70 |
71 | There are two ways to get started
72 |
73 | * Copy an existing example app from the [`examples`][ex] directory and then modify it
74 | * Add DataPub components into either an existing React App or into a newly created a React app created from scratch using e.g. `create-react-app`
75 |
76 | Of these two options the former is better when experimenting or for small changes. The latter is better if you are building a more complex application or integrating into an existing application.
77 |
78 | In order to add DataPub components into a newly created React application, follow the steps below:
79 |
80 | **Step 1:** create a new react application:
81 | ```bash
82 | create-react-app datapub-extend
83 | ```
84 | Change directory into datapub-extend and run the application to ensure it was created successfully:
85 | ```bash
86 | cd datapub-extend
87 | yarn start
88 | ```
89 | **Step 2:** Install Datapub
90 |
91 | ```bash
92 | yarn add datapub
93 | ```
94 | **Step 3:** In the App.js, initialises your app with the Resource editor
95 |
96 | ```javascript
97 | ...
98 | export class ResourceEditor extends React.Component {
99 | constructor(props) {
100 | super(props);
101 | this.state = {
102 | datasetId: this.props.config.datasetId,
103 | resourceId: "",
104 | resource: this.props.resource || {},
105 | ...
106 | };
107 | }
108 | ...
109 |
110 | ```
111 |
112 | **Step 4:** Also in App.js, import the components you need. For instance in the code below we import Upload and TableSchema component.
113 |
114 | ```javascript
115 | ...
116 | import { Upload, TableSchema } from "datapub";
117 | ...
118 | ```
119 | **Step 5:** In the render section of your resource editor, add the Upload and TableSchema components you just imported.
120 |
121 | ```javascript
122 | ...
123 |
124 |
125 | {this.state.resource.schema && (
126 |
130 | )}
131 |
132 | {!this.state.isResourceEdit ? (
133 |
134 | Save and Publish
135 |
136 | ) : (
137 |
138 |
143 | Delete
144 |
145 | Update
146 |
147 | )}
148 |
149 | ...
150 | ```
151 |
152 | See the full example with code and explanations [here](examples/table-schema)
153 |
154 | ---
155 |
156 | ## Developers
157 |
158 | ### Install
159 |
160 | Install a recent [Node](https://nodejs.org/en/) version.
161 |
162 | First, clone the repo via git:
163 |
164 | ```bash
165 | $ git clone git@github.com:datopian/datapub.git
166 | ```
167 |
168 | And then install dependencies with npm.
169 |
170 | ```bash
171 | $ cd datapub
172 | $ npm install
173 | ```
174 |
175 | ### Run
176 |
177 | Run the app in the development mode.
178 |
179 | ```bash
180 | $ npm run start
181 | ```
182 |
183 | Then open [http://localhost:3000/](http://localhost:3000/) to view it in the browser.
184 |
185 | The page will reload if you make edits.
186 |
187 | ### Storybook
188 |
189 | Storybook is a tool that prepares a development environment for UI components. It allows you to develop and design your graphical interfaces quickly, isolated, and independently. Making it possible to define different states for components, thus documenting their states.
190 |
191 | **Note**: Every push will run GitHub actions to deploy in GitHub pages. You can check online at https://datopian.github.io/datapub
192 |
193 | #### Run storybook
194 |
195 | ```bash
196 | $ npm run storybook
197 | ```
198 |
199 | or
200 |
201 | ```bash
202 | $ yarn storybook
203 | ```
204 |
205 | ### Run Tests
206 |
207 | ```bash
208 | $ npm test
209 | ```
210 |
211 | or
212 |
213 | ```bash
214 | $ yarn test
215 | ```
216 |
217 | To run tests + coverage
218 |
219 | ```bash
220 | $ yarn test:watch
221 | ```
222 |
223 | ## Contributing
224 |
225 | Please make sure to read the [CONTRIBUTING.md](CONTRIBUTING.md) Guide before making a pull request.
226 |
227 | ## License
228 |
229 | This project is licensed under the MIT License - see the [LICENSE](License) file for details
230 |
--------------------------------------------------------------------------------
/dist/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | .App {
16 | text-align: center;
17 | min-height: 100vh;
18 | color: #222;
19 | }
20 |
21 | .upload-wrapper {
22 | width: 100%;
23 | min-width: 360px;
24 | max-width: 960px;
25 | margin: 0 auto;
26 | background-color: #fff;
27 | padding: 32px 24px;
28 | border-radius: 12px;
29 | min-height: 60%;
30 | display: grid;
31 | grid-template-columns: 1fr 1fr;
32 | grid-template-rows: auto 300px auto auto;
33 | grid-template-areas:
34 | "header header"
35 | "uploadArea uploadArea"
36 | "editArea editArea";
37 | row-gap: 20px;
38 | column-gap: 20px;
39 | }
40 |
41 | .upload-header {
42 | grid-area: header;
43 | border-bottom: 1px solid #e3ebff;
44 | }
45 |
46 | .upload-header__title {
47 | color: #3f4656;
48 | font-weight: 400;
49 | }
50 |
51 | .upload-area {
52 | grid-area: uploadArea;
53 | display: grid;
54 | grid-template-columns: 40% auto;
55 | grid-template-rows: 1fr 1fr;
56 | grid-template-areas: "drop info";
57 | align-items: center;
58 | }
59 |
60 | .upload-area__info {
61 | grid-area: info;
62 | padding-top: 20px;
63 | }
64 |
65 | .upload-list {
66 | padding: 0;
67 | }
68 |
69 | .list-item {
70 | display: flex;
71 | }
72 |
73 | .upload-list-item {
74 | margin: 0 auto;
75 | background-color: #fff;
76 | padding: 8px 24px;
77 | list-style: none;
78 | max-width: 480px;
79 | width: 85%;
80 | border-top-left-radius: 8px;
81 | border-bottom-left-radius: 8px;
82 | color: #222;
83 | display: flex;
84 | align-items: center;
85 | justify-content: space-between;
86 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
87 | }
88 |
89 | .upload-file-name {
90 | max-width: 19ch;
91 | white-space: nowrap;
92 | overflow: hidden;
93 | text-overflow: ellipsis;
94 | color: #3f4656;
95 | }
96 |
97 | .upload-file-size {
98 | text-align: left;
99 | font-size: 0.75rem;
100 | color: lightslategrey;
101 | }
102 |
103 | .upload-message {
104 | color: #3f4656;
105 | }
106 |
107 | .upload-switcher {
108 | grid-area: switcher;
109 | }
110 |
111 | .upload-edit-area {
112 | grid-area: editArea;
113 | }
114 |
115 | .btn {
116 | max-width: 220px;
117 | margin: 2rem;
118 | border-radius: 5px;
119 | padding: 10px 25px;
120 | height: 45px;
121 | font-size: 1.2rem;
122 | color: #fff;
123 | background-color: #00A7E1;
124 | outline: none;
125 | border: none;
126 | cursor: pointer;
127 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
128 | }
129 |
130 | .btn:disabled {
131 | background: #e3e9ed !important;
132 | cursor: default;
133 | }
134 |
135 | .btn:hover {
136 | background-color: #009ace;
137 | }
138 |
139 | .btn-delete {
140 | margin: 2rem;
141 | border-radius: 5px;
142 | padding: 10px 25px;
143 | height: 45px;
144 | font-size: 1.2rem;
145 | color: #fff;
146 | outline: none;
147 | border: none;
148 | cursor: pointer;
149 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
150 | background-color: #F11301;
151 | }
152 |
153 | .btn-delete :disabled {
154 | background: #e3e9ed !important;
155 | cursor: default;
156 | }
157 |
158 | .btn-delete:hover {
159 | background-color: #C70E00;
160 | }
161 |
162 | .resource-edit-actions {
163 | display: flex;
164 | justify-content: space-between;
165 | }
--------------------------------------------------------------------------------
/dist/App.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _enzyme = require("enzyme");
6 |
7 | var _App = _interopRequireDefault(require("./App"));
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe(" ", function () {
12 | it("should render in add a resource", function () {
13 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_App.default, null));
14 | expect(wrapper.find(".btn")).toHaveLength(1);
15 | expect(wrapper.find(".btn-delete")).toHaveLength(0);
16 | });
17 | });
--------------------------------------------------------------------------------
/dist/assets/images/computing-cloud.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/dist/components/Choose/Choose.css:
--------------------------------------------------------------------------------
1 | .upload-choose {
2 |
3 | }
4 |
5 | .choose-btn {
6 | padding: 10px;
7 | width: 90%;
8 | background-color: #4FA8FD;
9 | border: none;
10 | border-radius: 3px;
11 | color: #fff;
12 | font-weight: 500;
13 | cursor: pointer;
14 | outline: 0;
15 | font-weight: bold;
16 | }
17 | .choose-text {
18 | font-size: 1rem;
19 | font-weight: 300;
20 | }
21 |
--------------------------------------------------------------------------------
/dist/components/Choose/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.default = void 0;
9 |
10 | var _react = _interopRequireWildcard(require("react"));
11 |
12 | var _propTypes = _interopRequireDefault(require("prop-types"));
13 |
14 | var _InputFile = _interopRequireDefault(require("../InputFile"));
15 |
16 | var _InputUrl = _interopRequireDefault(require("../InputUrl"));
17 |
18 | require("./Choose.css");
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
23 |
24 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25 |
26 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
27 |
28 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
29 |
30 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
31 |
32 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
33 |
34 | function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
35 |
36 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
37 |
38 | var Choose = function Choose(_ref) {
39 | var onChangeUrl = _ref.onChangeUrl,
40 | onChangeHandler = _ref.onChangeHandler;
41 |
42 | var _useState = (0, _react.useState)(false),
43 | _useState2 = _slicedToArray(_useState, 2),
44 | uploadOption = _useState2[0],
45 | setUploadOption = _useState2[1];
46 |
47 | return /*#__PURE__*/_react.default.createElement("div", {
48 | className: "upload-choose"
49 | }, uploadOption ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, uploadOption === "file" && /*#__PURE__*/_react.default.createElement(_InputFile.default, {
50 | onChangeHandler: onChangeHandler
51 | }), uploadOption === "url" && /*#__PURE__*/_react.default.createElement(_InputUrl.default, {
52 | onChangeUrl: onChangeUrl
53 | })) : /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("button", {
54 | className: "choose-btn",
55 | onClick: function onClick() {
56 | return setUploadOption("file");
57 | }
58 | }, "Choose a file to Upload "), /*#__PURE__*/_react.default.createElement("p", {
59 | className: "choose-text"
60 | }, "OR"), /*#__PURE__*/_react.default.createElement("button", {
61 | className: "choose-btn",
62 | onClick: function onClick() {
63 | return setUploadOption("url");
64 | }
65 | }, "Link a file already online")));
66 | };
67 |
68 | Choose.propTypes = {
69 | onChangeUrl: _propTypes.default.func.isRequired,
70 | onChangeHandler: _propTypes.default.func.isRequired
71 | };
72 | var _default = Choose;
73 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/InputFile/InputFile.css:
--------------------------------------------------------------------------------
1 |
2 | .upload-area__drop {
3 | position: relative;
4 | text-align: center;
5 | display: flex;
6 | justify-content: center;
7 | margin-top: 20px;
8 | height: 220px;
9 | }
10 |
11 | .upload-area__drop__input {
12 | grid-area: drop;
13 | padding: 1rem 1rem;
14 | position: relative;
15 | display: grid;
16 | grid-template-columns: auto auto;
17 | align-items: center;
18 | min-height: 200px;
19 | border: 1px dashed #3f4c6b;
20 | margin: 0px auto;
21 | width: 95%;
22 | border-radius: 8px;
23 | cursor: pointer;
24 | outline: 0;
25 | box-sizing: border-box;
26 | z-index: 10;
27 | color: transparent;
28 | }
29 |
30 | .upload-area__drop__input::-webkit-file-upload-button {
31 | flex-shrink: 0;
32 | font-size: 0.65rem;
33 | color: #ffffff;
34 | border-radius: 3px;
35 | padding: 8px 15px;
36 | margin: 0;
37 | text-transform: uppercase;
38 | text-align: center;
39 | border: none;
40 | outline: 0;
41 | margin: 0 auto;
42 | visibility: hidden;
43 | }
44 |
45 | .upload-area__drop__icon {
46 | width: 80px;
47 | position: absolute;
48 | top: 20%;
49 | left: 40%;
50 | cursor: pointer;
51 | fill: #e3ebff;
52 | }
53 |
54 | .upload-area__drop__text {
55 | color: lightslategrey;
56 | opacity: 0.7;
57 | font-weight: 400;
58 | position: absolute;
59 | top: 55%;
60 | font-size: 0.78rem;
61 | cursor: pointer;
62 | }
--------------------------------------------------------------------------------
/dist/components/InputFile/InputFile.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _enzyme = require("enzyme");
6 |
7 | var _ = _interopRequireDefault(require("."));
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe(" ", function () {
12 | var onChangeHandler = jest.fn(function () {
13 | return "sample";
14 | });
15 | it("render InputFile without crashing", function () {
16 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
17 | onChangeHandler: onChangeHandler
18 | }));
19 | expect(wrapper.contains("Drag and drop your files")).toEqual(true);
20 | });
21 | it("can select a file", function () {
22 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
23 | onChangeHandler: onChangeHandler
24 | }));
25 | var input = wrapper.find("input");
26 | input.simulate("change", {
27 | target: {
28 | files: ["sample.csv"]
29 | }
30 | });
31 | expect(onChangeHandler).toHaveBeenCalled();
32 | });
33 | });
--------------------------------------------------------------------------------
/dist/components/InputFile/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = _interopRequireDefault(require("prop-types"));
11 |
12 | require("./InputFile.css");
13 |
14 | var _computingCloud = _interopRequireDefault(require("../../assets/images/computing-cloud.svg"));
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17 |
18 | var InputFile = function InputFile(_ref) {
19 | var onChangeHandler = _ref.onChangeHandler;
20 | return /*#__PURE__*/_react.default.createElement("div", {
21 | className: "upload-area__drop"
22 | }, /*#__PURE__*/_react.default.createElement("input", {
23 | className: "upload-area__drop__input",
24 | type: "file",
25 | name: "file",
26 | onChange: onChangeHandler
27 | }), /*#__PURE__*/_react.default.createElement("img", {
28 | className: "upload-area__drop__icon",
29 | src: "https://www.shareicon.net/data/256x256/2015/09/05/96087_cloud_512x512.png",
30 | alt: "upload-icon"
31 | }), /*#__PURE__*/_react.default.createElement("span", {
32 | className: "upload-area__drop__text"
33 | }, "Drag and drop your files", /*#__PURE__*/_react.default.createElement("br", null), "or ", /*#__PURE__*/_react.default.createElement("br", null), "click to select"));
34 | };
35 |
36 | InputFile.propTypes = {
37 | onChangeHandler: _propTypes.default.func.isRequired
38 | };
39 | var _default = InputFile;
40 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/InputUrl/InputUrl.css:
--------------------------------------------------------------------------------
1 | .upload-area__url {
2 | text-align: left;
3 | width: 95%;
4 | margin: 0 auto;
5 | }
6 |
7 | .upload-area__url__label {
8 | display: block;
9 | padding: 4px;
10 | }
11 |
12 | .upload-area__url__input {
13 | width: 100%;
14 | height: 40px;
15 | border-radius: 5px;
16 | box-shadow: none;
17 | border: 1px solid #ced6e0;
18 | transition: all 0.3s ease-in-out;
19 | font-size: 18px;
20 | padding: 5px 15px;
21 | background: none;
22 | color: #1a3b5d;
23 | font-family: "Source Sans Pro", sans-serif;
24 | box-sizing: border-box;
25 | font-size: 1rem;
26 | }
27 |
--------------------------------------------------------------------------------
/dist/components/InputUrl/InputUrl.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _enzyme = require("enzyme");
6 |
7 | var _ = _interopRequireDefault(require("."));
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe(" ", function () {
12 | var onChangeUrl = jest.fn(function () {
13 | return "https://www.datopian.com";
14 | });
15 | it("render InputUrl without crashing", function () {
16 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
17 | onChangeUrl: onChangeUrl
18 | }));
19 | expect(wrapper.contains("URL:")).toEqual(true);
20 | });
21 | it("can edit url", function () {
22 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
23 | onChangeUrl: onChangeUrl
24 | }));
25 | var input = wrapper.find("input");
26 | input.simulate("blur", {
27 | target: {
28 | value: "https://www.datopian.com"
29 | }
30 | });
31 | expect(onChangeUrl).toHaveBeenCalled();
32 | });
33 | });
--------------------------------------------------------------------------------
/dist/components/InputUrl/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = _interopRequireDefault(require("prop-types"));
11 |
12 | require("./InputUrl.css");
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | var InputUrl = function InputUrl(_ref) {
17 | var onChangeUrl = _ref.onChangeUrl;
18 |
19 | var handleKeyPress = function handleKeyPress(event) {
20 | if (event.keyCode === 13) {
21 | event.target.blur();
22 | }
23 | };
24 |
25 | return /*#__PURE__*/_react.default.createElement("div", {
26 | className: "upload-area__url"
27 | }, /*#__PURE__*/_react.default.createElement("label", {
28 | className: "upload-area__url__label",
29 | htmlFor: "input-url"
30 | }, "URL:"), /*#__PURE__*/_react.default.createElement("input", {
31 | className: "upload-area__url__input",
32 | id: "input-url",
33 | type: "url",
34 | name: "input-url",
35 | onBlur: onChangeUrl,
36 | onKeyDown: function onKeyDown(event) {
37 | return handleKeyPress(event);
38 | },
39 | placeholder: "https://www.data.com/sample.csv"
40 | }));
41 | };
42 |
43 | InputUrl.propTypes = {
44 | onChangeUrl: _propTypes.default.func.isRequired
45 | };
46 | var _default = InputUrl;
47 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/Metadata/Metadata.css:
--------------------------------------------------------------------------------
1 | .metadata-form {
2 | display: grid;
3 | grid-template-columns: 1fr 1fr;
4 | column-gap: 20px;
5 | }
6 |
7 | .metadata-name {
8 | color: #1a3b5d;
9 | text-align: left;
10 | grid-area: name;
11 | }
12 |
13 | .metadata-label {
14 | font-size: 14px;
15 | margin-bottom: 5px;
16 | margin-left: 5px;
17 | text-align: left;
18 | font-weight: 500;
19 | color: #1a3b5d;
20 | width: 100%;
21 | display: block;
22 | user-select: none;
23 | }
24 |
25 | .metadata-input {
26 | margin-bottom: 20px;
27 | }
28 |
29 | .metadata-input__input {
30 | width: 100%;
31 | height: 40px;
32 | border-radius: 5px;
33 | box-shadow: none;
34 | border: 1px solid #ced6e0;
35 | transition: all 0.3s ease-in-out;
36 | font-size: 18px;
37 | padding: 5px 15px;
38 | background: none;
39 | color: #1a3b5d;
40 | font-family: "Source Sans Pro", sans-serif;
41 | box-sizing: border-box;
42 | }
43 |
44 | .btn {
45 | margin-top: 17px;
46 | border-radius: 5px;
47 | padding: 10px;
48 | width: 50%;
49 | height: 45px;
50 | font-size: 1.2rem;
51 | color: #fff;
52 | background-color: #00A7E1;
53 | outline: none;
54 | border: none;
55 | cursor: pointer;
56 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
57 | }
58 |
59 | .btn:disabled {
60 | background: #e3e9ed !important;
61 | cursor: default;
62 | }
63 |
64 | .btn:hover {
65 | background-color: #009ace;
66 | }
--------------------------------------------------------------------------------
/dist/components/Metadata/Metadata.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _reactDom = _interopRequireDefault(require("react-dom"));
6 |
7 | var _enzyme = require("enzyme");
8 |
9 | var _ = _interopRequireDefault(require("."));
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12 |
13 | describe(" ", function () {
14 | var handleChange = jest.fn(2);
15 | var handleSubmit = jest.fn();
16 | var deleteResource = jest.fn();
17 | var updateResource = jest.fn();
18 | it("render Metadata without crashing", function () {
19 | var div = document.createElement("div");
20 |
21 | _reactDom.default.render( /*#__PURE__*/_react.default.createElement(_.default, {
22 | metadata: {},
23 | handleChange: handleChange,
24 | handleSubmit: handleSubmit,
25 | deleteResource: deleteResource,
26 | updateResource: updateResource,
27 | uploadSuccess: false,
28 | isResourceEdit: false
29 | }), div);
30 | });
31 | it("auto-populate input fields", function () {
32 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
33 | metadata: {
34 | title: "sample",
35 | format: "csv",
36 | description: "Lorem ...",
37 | restricted: "private",
38 | encoding: "utf-8"
39 | },
40 | handleChange: handleChange,
41 | handleSubmit: handleSubmit,
42 | deleteResource: deleteResource,
43 | updateResource: updateResource,
44 | uploadSuccess: false,
45 | isResourceEdit: false
46 | }));
47 | var inputTitle = wrapper.find("#title");
48 | var inputFormat = wrapper.find("#format");
49 | var inputDescription = wrapper.find("#description");
50 | var inputRestricted = wrapper.find("#restricted");
51 | var inputEncoding = wrapper.find("#encoding");
52 | expect(inputTitle.props().value).toEqual("sample");
53 | expect(inputFormat.props().value).toEqual("csv");
54 | expect(inputDescription.props().value).toEqual("Lorem ...");
55 | expect(inputRestricted.props().value).toEqual("private");
56 | expect(inputEncoding.props().value).toEqual("utf-8");
57 | });
58 | });
--------------------------------------------------------------------------------
/dist/components/Metadata/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = _interopRequireDefault(require("prop-types"));
11 |
12 | require("./Metadata.css");
13 |
14 | var _encode = _interopRequireDefault(require("../../db/encode.json"));
15 |
16 | var _resource_formats = _interopRequireDefault(require("../../db/resource_formats.json"));
17 |
18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19 |
20 | //TODO: add the custom fields as a props and render it in metadata component
21 | var customFields = [{
22 | label: "Access Restriction",
23 | name: "restricted",
24 | input_type: "select",
25 | values: ['{"level": "public"}', '{"level": "private"}'],
26 | options: ["Public", "Private"]
27 | }];
28 |
29 | var Metadata = function Metadata(_ref) {
30 | var metadata = _ref.metadata,
31 | handleChange = _ref.handleChange;
32 | return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("h3", {
33 | className: "metadata-name"
34 | }, metadata.path), /*#__PURE__*/_react.default.createElement("div", {
35 | className: "metadata-form"
36 | }, /*#__PURE__*/_react.default.createElement("div", {
37 | className: "metadata-input"
38 | }, /*#__PURE__*/_react.default.createElement("label", {
39 | className: "metadata-label",
40 | htmlFor: "title"
41 | }, "Title:"), /*#__PURE__*/_react.default.createElement("input", {
42 | className: "metadata-input__input",
43 | type: "text",
44 | name: "title",
45 | id: "title",
46 | value: metadata.title || metadata.name,
47 | onChange: handleChange
48 | })), /*#__PURE__*/_react.default.createElement("div", {
49 | className: "metadata-input"
50 | }, /*#__PURE__*/_react.default.createElement("label", {
51 | className: "metadata-label",
52 | htmlFor: "description"
53 | }, "Description:"), /*#__PURE__*/_react.default.createElement("input", {
54 | className: "metadata-input__input",
55 | type: "text",
56 | name: "description",
57 | id: "description",
58 | value: metadata.description || "",
59 | onChange: handleChange
60 | })), /*#__PURE__*/_react.default.createElement("div", {
61 | className: "metadata-input"
62 | }, /*#__PURE__*/_react.default.createElement("label", {
63 | className: "metadata-label",
64 | htmlFor: "encoding"
65 | }, "Encoding:"), /*#__PURE__*/_react.default.createElement("select", {
66 | className: "metadata-input__input",
67 | name: "encoding",
68 | id: "encoding",
69 | value: metadata.encoding || "",
70 | onChange: handleChange,
71 | required: true
72 | }, /*#__PURE__*/_react.default.createElement("option", {
73 | value: "",
74 | disabled: true
75 | }, "Select..."), _encode.default.map(function (item) {
76 | return /*#__PURE__*/_react.default.createElement("option", {
77 | key: "format-".concat(item.value),
78 | value: item.value
79 | }, item.label);
80 | }))), /*#__PURE__*/_react.default.createElement("div", {
81 | className: "metadata-input"
82 | }, /*#__PURE__*/_react.default.createElement("label", {
83 | className: "metadata-label",
84 | htmlFor: "format"
85 | }, "Format:"), /*#__PURE__*/_react.default.createElement("select", {
86 | className: "metadata-input__input",
87 | name: "format",
88 | id: "format",
89 | value: (metadata.format || "").toLowerCase(),
90 | onChange: handleChange,
91 | required: true
92 | }, /*#__PURE__*/_react.default.createElement("option", {
93 | value: "",
94 | disabled: true
95 | }, "Select..."), _resource_formats.default.map(function (item) {
96 | return /*#__PURE__*/_react.default.createElement("option", {
97 | key: "format-".concat(item[0]),
98 | value: item[0].toLowerCase()
99 | }, item[0]);
100 | }))), customFields && customFields.map(function (item) {
101 | return /*#__PURE__*/_react.default.createElement("div", {
102 | key: "metadata-custom-".concat(item.name),
103 | className: "metadata-input"
104 | }, /*#__PURE__*/_react.default.createElement("label", {
105 | className: "metadata-label",
106 | htmlFor: "format"
107 | }, item.label, ":"), /*#__PURE__*/_react.default.createElement("select", {
108 | className: "metadata-input__input",
109 | name: item.name,
110 | id: item.name,
111 | value: metadata[item.name] || "",
112 | onChange: handleChange,
113 | required: true
114 | }, /*#__PURE__*/_react.default.createElement("option", {
115 | value: "",
116 | disabled: true
117 | }, "Select..."), item.options.map(function (option, index) {
118 | return /*#__PURE__*/_react.default.createElement("option", {
119 | key: "".concat(item.name, "-").concat(index),
120 | value: item.values[index]
121 | }, option);
122 | })));
123 | })));
124 | };
125 |
126 | Metadata.propTypes = {
127 | metadata: _propTypes.default.object.isRequired,
128 | handleChange: _propTypes.default.func.isRequired
129 | };
130 | var _default = Metadata;
131 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/ProgressBar/ProgressBar.css:
--------------------------------------------------------------------------------
1 | .svg{
2 | display: block;
3 | margin: 20px auto;
4 | max-width: 100%;
5 | }
6 |
7 | .svg-circle-bg {
8 | fill: none;
9 | }
10 |
11 | .svg-circle {
12 | fill: none;
13 | }
14 |
15 | .svg-circle-text {
16 | font-size: 0.68rem;
17 | text-anchor: middle;
18 | margin-top: 30px;
19 | fill:#3F4656;
20 | font-weight: bold;
21 | }
22 |
23 | .time-remaining {
24 | font-size: 0.55rem;
25 | float: left;
26 | margin-top: -13px;
27 | color: #3F4656;
28 | }
--------------------------------------------------------------------------------
/dist/components/ProgressBar/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.default = void 0;
9 |
10 | var _react = _interopRequireWildcard(require("react"));
11 |
12 | var _propTypes = _interopRequireDefault(require("prop-types"));
13 |
14 | require("./ProgressBar.css");
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17 |
18 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
19 |
20 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
21 |
22 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
23 |
24 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
25 |
26 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
27 |
28 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
29 |
30 | function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
31 |
32 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
33 |
34 | var ProgressBar = function ProgressBar(props) {
35 | var _useState = (0, _react.useState)(0),
36 | _useState2 = _slicedToArray(_useState, 2),
37 | offset = _useState2[0],
38 | setOffset = _useState2[1];
39 |
40 | var circleRef = (0, _react.useRef)(null);
41 | var size = props.size,
42 | progress = props.progress,
43 | strokeWidth = props.strokeWidth,
44 | circleOneStroke = props.circleOneStroke,
45 | circleTwoStroke = props.circleTwoStroke,
46 | timeRemaining = props.timeRemaining;
47 | var center = size / 2;
48 | var radius = size / 2 - strokeWidth / 2;
49 | var circumference = 2 * Math.PI * radius;
50 | (0, _react.useEffect)(function () {
51 | var progressOffset = (100 - progress) / 100 * circumference;
52 | setOffset(progressOffset);
53 | circleRef.current.style = "transition: stroke-dashoffset 850ms ease-in-out";
54 | }, [setOffset, progress, circumference, offset]);
55 | return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("svg", {
56 | className: "svg",
57 | width: size,
58 | height: size
59 | }, /*#__PURE__*/_react.default.createElement("circle", {
60 | className: "svg-circle-bg",
61 | stroke: circleOneStroke,
62 | cx: center,
63 | cy: center,
64 | r: radius,
65 | strokeWidth: strokeWidth
66 | }), /*#__PURE__*/_react.default.createElement("circle", {
67 | className: "svg-circle",
68 | ref: circleRef,
69 | stroke: circleTwoStroke,
70 | cx: center,
71 | cy: center,
72 | r: radius,
73 | strokeWidth: strokeWidth,
74 | strokeDasharray: circumference,
75 | strokeDashoffset: offset
76 | }), /*#__PURE__*/_react.default.createElement("text", {
77 | x: "".concat(center),
78 | y: "".concat(center + 2),
79 | className: "svg-circle-text"
80 | }, progress, "%")), timeRemaining > 0 && /*#__PURE__*/_react.default.createElement("span", {
81 | className: "time-remaining"
82 | }, timeRemaining > 60 ? "".concat(Math.floor(timeRemaining / 60), " minute").concat(Math.floor(timeRemaining / 60) > 1 ? 's' : '') : "".concat(Math.floor(timeRemaining), " second").concat(timeRemaining > 1 ? 's' : ''), " left"));
83 | };
84 |
85 | ProgressBar.propTypes = {
86 | size: _propTypes.default.number.isRequired,
87 | progress: _propTypes.default.number.isRequired,
88 | strokeWidth: _propTypes.default.number.isRequired,
89 | circleOneStroke: _propTypes.default.string.isRequired,
90 | circleTwoStroke: _propTypes.default.string.isRequired
91 | };
92 | var _default = ProgressBar;
93 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/Switcher/Switcher.css:
--------------------------------------------------------------------------------
1 | .switch-wrapper {
2 | color: #222222;
3 | display: grid;
4 | grid-template-columns: 160px 200px 160px;
5 | grid-template-areas: "switchOne divLine switchTwo";
6 | width: 80%;
7 | align-items: center;
8 | justify-items: center;
9 | align-content: center;
10 | justify-content: center;
11 | margin: 1rem auto;
12 | }
13 |
14 | .switch-number {
15 | border-radius: 50%;
16 | width: 20px;
17 | height: 20px;
18 | padding: 3px;
19 | text-align: center;
20 | margin-bottom: 1.2rem;
21 | margin: 0 auto;
22 | cursor: pointer;
23 | }
24 |
25 | .switch-one {
26 | grid-area: switchOne;
27 | }
28 |
29 | .switch-two {
30 | grid-area: switchTwo;
31 | }
32 |
33 | .switch-number-selected {
34 | background-color: #00A7E1;
35 | color: #ffffff;
36 | }
37 |
38 | .switch-number-disabled {
39 | border: 1px solid #e3ebff;
40 | }
41 |
42 | .switch-description {
43 | margin-top: 1rem;
44 | width: 100%;
45 | cursor: pointer;
46 | }
47 |
48 | .switch-description-active {
49 | font-weight: bold;
50 | }
51 |
52 | .divider-line {
53 | grid-area: divLine;
54 | height: 2px;
55 | background-color: #e3ebff;
56 | width: 100%;
57 | margin-bottom: 3rem;
58 | }
59 |
--------------------------------------------------------------------------------
/dist/components/Switcher/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = _interopRequireDefault(require("prop-types"));
11 |
12 | require("./Switcher.css");
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | var Switcher = function Switcher(props) {
17 | return /*#__PURE__*/_react.default.createElement("div", {
18 | className: "switch-wrapper"
19 | }, /*#__PURE__*/_react.default.createElement("div", {
20 | onClick: function onClick() {
21 | return props.switcher('metadata');
22 | }
23 | }, /*#__PURE__*/_react.default.createElement("div", {
24 | className: "switch-number ".concat(props.metadataOrSchema === 'metadata' ? "switch-number-selected" : "switch-number-disabled")
25 | }, "1"), /*#__PURE__*/_react.default.createElement("p", {
26 | className: "switch-description \"switch-description-active\"}"
27 | }, "Edit Metadata")), /*#__PURE__*/_react.default.createElement("div", {
28 | onClick: function onClick() {
29 | return props.switcher('schema');
30 | }
31 | }, /*#__PURE__*/_react.default.createElement("div", {
32 | className: "switch-number ".concat(props.metadataOrSchema === 'schema' ? "switch-number-selected" : "switch-number-disabled")
33 | }, "2"), /*#__PURE__*/_react.default.createElement("p", {
34 | className: "switch-description \"switch-description-active\"}"
35 | }, "Edit Schema")), /*#__PURE__*/_react.default.createElement("div", {
36 | className: "divider-line"
37 | }));
38 | };
39 |
40 | Switcher.propTypes = {
41 | metadataOrSchema: _propTypes.default.string.isRequired
42 | };
43 | var _default = Switcher;
44 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/TableSchema/TableSchema.css:
--------------------------------------------------------------------------------
1 | .table-container {
2 | margin-top: 2.6rem;
3 | margin-bottom: 2rem;
4 | display: flex;
5 | }
6 |
7 | .table-schema-info_container {
8 | overflow: overlay;
9 | }
10 |
11 | .table-schema-info_container::-webkit-scrollbar-track {
12 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
13 | border-radius: 10px;
14 | background-color: #fff;
15 | }
16 |
17 | .table-schema-info_container::-webkit-scrollbar {
18 | width: 8px;
19 | height: 8px;
20 | background-color: #fff;
21 | }
22 |
23 | .table-schema-info_container::-webkit-scrollbar-thumb {
24 | border-radius: 10px;
25 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
26 | background-color: #e3e9ed;
27 | }
28 |
29 | .table-schema-info_table {
30 | width: auto !important;
31 | }
32 |
33 | .table-schema-help {
34 | vertical-align: middle;
35 | }
36 |
37 | .table-schema-help_row {
38 | padding: 1.3rem;
39 | }
40 |
41 | .table-thead-tr {
42 | height: 50px;
43 | border-radius: 8px;
44 | }
45 |
46 | .table-thead-th {
47 | background: #f1f1f1;
48 | color: #363844;
49 | font-weight: bold;
50 | text-align: center;
51 | vertical-align: middle;
52 | position: sticky;
53 | top: 0;
54 | z-index: 10;
55 | }
56 |
57 | .table-tbody-help-tr {
58 | color: #363844;
59 | height: 40px;
60 | }
61 |
62 | .table-tbody-help-td {
63 | vertical-align: middle;
64 | font-weight: bold;
65 | }
66 |
67 | .table-tbody-help-td-empty {
68 | height: 50px;
69 | }
70 |
71 | .table-tbody-td {
72 | padding: 5px;
73 | border: solid 1px #f1f1f1;
74 | background: #fff;
75 | max-width: 100px;
76 | overflow: hidden;
77 | text-overflow: ellipsis;
78 | white-space: nowrap;
79 | font-size: 14px;
80 | }
81 |
82 | .table-tbody-input {
83 | border: none;
84 | padding: 0 10px 0 10px;
85 | height: 40px;
86 | }
87 |
88 | .table-tbody-select {
89 | position: relative;
90 | display: inline-block;
91 | vertical-align: middle;
92 | background-color: #fafafa;
93 | width: 100%;
94 | border: none;
95 | padding: 10px;
96 | height: 40px;
97 | }
98 |
99 | .table-btn-save {
100 | padding: 10px;
101 | width: 90%;
102 | background-color: #4fa8fd;
103 | border: none;
104 | border-radius: 3px;
105 | color: #fff;
106 | font-weight: 500;
107 | cursor: pointer;
108 | outline: 0;
109 | }
110 |
111 | .table-btn-save:disabled {
112 | background: #e3e9ed !important;
113 | cursor: default;
114 | }
115 |
--------------------------------------------------------------------------------
/dist/components/TableSchema/TableSchema.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _enzyme = require("enzyme");
6 |
7 | var _ = _interopRequireDefault(require("."));
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe(" ", function () {
12 | var onChange = jest.fn(function () {
13 | return "https://www.datopian.com";
14 | });
15 | it("render TableSchema without crashing", function () {
16 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, {
17 | data: [],
18 | schema: {
19 | fields: []
20 | }
21 | }));
22 | expect(wrapper.contains("Title")).toEqual(true);
23 | expect(wrapper.contains("Description")).toEqual(true);
24 | expect(wrapper.contains("Type")).toEqual(true);
25 | expect(wrapper.contains("Format")).toEqual(true);
26 | });
27 | });
--------------------------------------------------------------------------------
/dist/components/TableSchema/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.default = void 0;
9 |
10 | var _react = _interopRequireWildcard(require("react"));
11 |
12 | var _propTypes = _interopRequireDefault(require("prop-types"));
13 |
14 | var _reactTable = require("react-table");
15 |
16 | var _types = _interopRequireDefault(require("../../db/types.json"));
17 |
18 | require("./TableSchema.css");
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
23 |
24 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25 |
26 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
27 |
28 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
29 |
30 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
31 |
32 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33 |
34 | function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
35 |
36 | function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
37 |
38 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
39 |
40 | function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
41 |
42 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
43 |
44 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
45 |
46 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
47 |
48 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
49 |
50 | function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
51 |
52 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
53 |
54 | var TableSchema = function TableSchema(props) {
55 | var _useState = (0, _react.useState)(props.schema),
56 | _useState2 = _slicedToArray(_useState, 2),
57 | schema = _useState2[0],
58 | setSchema = _useState2[1]; // eslint-disable-next-line react-hooks/exhaustive-deps
59 |
60 |
61 | var data = _react.default.useMemo(function () {
62 | return _toConsumableArray(props.data);
63 | }, [schema]);
64 |
65 | var columnsSchema = schema.fields.map(function (item, index) {
66 | return {
67 | Header: item.name ? item.name : "column_".concat(index + 1),
68 | accessor: item.name ? item.name : "column_".concat(index + 1)
69 | };
70 | }); // eslint-disable-next-line react-hooks/exhaustive-deps
71 |
72 | var columns = _react.default.useMemo(function () {
73 | return _toConsumableArray(columnsSchema);
74 | }, [schema]);
75 |
76 | var _useTable = (0, _reactTable.useTable)({
77 | columns: columns,
78 | data: data
79 | }),
80 | getTableProps = _useTable.getTableProps,
81 | getTableBodyProps = _useTable.getTableBodyProps,
82 | headerGroups = _useTable.headerGroups,
83 | rows = _useTable.rows,
84 | prepareRow = _useTable.prepareRow;
85 |
86 | var handleChange = function handleChange(event, key, index) {
87 | var newSchema = _objectSpread({}, schema);
88 |
89 | newSchema.fields[index][key] = event.target.value;
90 | setSchema(newSchema);
91 | }; //if the the user upload a new file, will update the state
92 | //and render with the new values
93 |
94 |
95 | (0, _react.useEffect)(function () {
96 | setSchema(props.schema);
97 | }, [props.schema]);
98 |
99 | var renderEditSchemaField = function renderEditSchemaField(key) {
100 | if (key === "type") {
101 | return schema.fields.map(function (item, index) {
102 | return /*#__PURE__*/_react.default.createElement("td", {
103 | key: "schema-type-field-".concat(key, "-").concat(index)
104 | }, /*#__PURE__*/_react.default.createElement("select", {
105 | className: "table-tbody-select",
106 | value: item[key] || "",
107 | onChange: function onChange(event) {
108 | return handleChange(event, key, index);
109 | }
110 | }, _types.default.type.map(function (item, index) {
111 | return /*#__PURE__*/_react.default.createElement("option", {
112 | key: "schema-type-field-option-".concat(item, "-").concat(index),
113 | value: item
114 | }, item);
115 | })));
116 | });
117 | }
118 |
119 | return schema.fields.map(function (item, index) {
120 | return /*#__PURE__*/_react.default.createElement("td", {
121 | key: "schema-field-".concat(key, "-").concat(index)
122 | }, /*#__PURE__*/_react.default.createElement("input", {
123 | className: "table-tbody-input",
124 | type: "text",
125 | value: item[key],
126 | onChange: function onChange(event) {
127 | return handleChange(event, key, index);
128 | }
129 | }));
130 | });
131 | };
132 |
133 | return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
134 | className: "table-container"
135 | }, /*#__PURE__*/_react.default.createElement("table", {
136 | className: "table-schema-help"
137 | }, /*#__PURE__*/_react.default.createElement("tbody", null, /*#__PURE__*/_react.default.createElement("tr", {
138 | className: "table-tbody-help-tr"
139 | }, /*#__PURE__*/_react.default.createElement("td", {
140 | className: "table-tbody-help-td-empty"
141 | })), /*#__PURE__*/_react.default.createElement("tr", {
142 | className: "table-tbody-help-tr"
143 | }, /*#__PURE__*/_react.default.createElement("td", {
144 | className: "table-tbody-help-td"
145 | }, "Title")), /*#__PURE__*/_react.default.createElement("tr", {
146 | className: "table-tbody-help-tr"
147 | }, /*#__PURE__*/_react.default.createElement("td", {
148 | className: "table-tbody-help-td"
149 | }, "Description")), /*#__PURE__*/_react.default.createElement("tr", {
150 | className: "table-tbody-help-tr"
151 | }, /*#__PURE__*/_react.default.createElement("td", {
152 | className: "table-tbody-help-td"
153 | }, "Type")), /*#__PURE__*/_react.default.createElement("tr", {
154 | className: "table-tbody-help-tr"
155 | }, /*#__PURE__*/_react.default.createElement("td", {
156 | className: "table-tbody-help-td"
157 | }, "Format")))), /*#__PURE__*/_react.default.createElement("div", {
158 | className: "table-schema-info_container"
159 | }, /*#__PURE__*/_react.default.createElement("table", _extends({
160 | className: "table-schema-info_table"
161 | }, getTableProps()), /*#__PURE__*/_react.default.createElement("thead", null, headerGroups.map(function (headerGroup) {
162 | return /*#__PURE__*/_react.default.createElement("tr", _extends({
163 | className: "table-thead-tr"
164 | }, headerGroup.getHeaderGroupProps()), headerGroup.headers.map(function (column) {
165 | return /*#__PURE__*/_react.default.createElement("th", _extends({
166 | className: "table-thead-th"
167 | }, column.getHeaderProps()), column.render("Header"));
168 | }));
169 | })), /*#__PURE__*/_react.default.createElement("tbody", getTableBodyProps(), /*#__PURE__*/_react.default.createElement("tr", {
170 | className: "table-tbody-tr-help"
171 | }, renderEditSchemaField("title")), /*#__PURE__*/_react.default.createElement("tr", {
172 | className: "table-tbody-tr-help"
173 | }, renderEditSchemaField("description")), /*#__PURE__*/_react.default.createElement("tr", null, renderEditSchemaField("type")), /*#__PURE__*/_react.default.createElement("tr", null, renderEditSchemaField("format")), rows.map(function (row) {
174 | prepareRow(row);
175 | return /*#__PURE__*/_react.default.createElement("tr", row.getRowProps(), row.cells.map(function (cell) {
176 | return /*#__PURE__*/_react.default.createElement("td", _extends({}, cell.getCellProps(), {
177 | className: "table-tbody-td"
178 | }), cell.render("Cell"));
179 | }));
180 | }))))));
181 | };
182 |
183 | TableSchema.propTypes = {
184 | schema: _propTypes.default.object.isRequired,
185 | data: _propTypes.default.array.isRequired
186 | };
187 | var _default = TableSchema;
188 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/components/Upload/Upload.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _react2 = require("@testing-library/react");
6 |
7 | var _reactDom = _interopRequireDefault(require("react-dom"));
8 |
9 | var _enzyme = require("enzyme");
10 |
11 | var _ = _interopRequireDefault(require("."));
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | describe(" ", function () {
16 | it("render Upload without crashing", function () {
17 | var div = document.createElement("div");
18 |
19 | _reactDom.default.render( /*#__PURE__*/_react.default.createElement(_.default, null), div);
20 | });
21 | it.skip('can select a file', function () {
22 | var wrapper = (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_.default, null));
23 | var input = wrapper.find('input');
24 | input.simulate('change', {
25 | target: {
26 | files: ['sample.csv']
27 | }
28 | });
29 | expect(wrapper.state().selectedFile).toEqual('sample.csv');
30 | });
31 | });
--------------------------------------------------------------------------------
/dist/components/Upload/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var data = _interopRequireWildcard(require("frictionless.js"));
11 |
12 | var _streamToArray = _interopRequireDefault(require("stream-to-array"));
13 |
14 | var _ProgressBar = _interopRequireDefault(require("../ProgressBar"));
15 |
16 | var _utils = require("../../utils");
17 |
18 | var _Choose = _interopRequireDefault(require("../Choose"));
19 |
20 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
21 |
22 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
23 |
24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25 |
26 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
27 |
28 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
29 |
30 | function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
31 |
32 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
33 |
34 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
35 |
36 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
37 |
38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
39 |
40 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
41 |
42 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
43 |
44 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
45 |
46 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
47 |
48 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
49 |
50 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
51 |
52 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
53 |
54 | var Upload = /*#__PURE__*/function (_React$Component) {
55 | _inherits(Upload, _React$Component);
56 |
57 | var _super = _createSuper(Upload);
58 |
59 | function Upload(props) {
60 | var _this;
61 |
62 | _classCallCheck(this, Upload);
63 |
64 | _this = _super.call(this, props);
65 |
66 | _defineProperty(_assertThisInitialized(_this), "onChangeHandler", /*#__PURE__*/function () {
67 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(event) {
68 | var _this$state, formattedSize, selectedFile, file, rowStream, hash;
69 |
70 | return regeneratorRuntime.wrap(function _callee$(_context) {
71 | while (1) {
72 | switch (_context.prev = _context.next) {
73 | case 0:
74 | _this$state = _this.state, formattedSize = _this$state.formattedSize, selectedFile = _this$state.selectedFile;
75 |
76 | if (!(event.target.files.length > 0)) {
77 | _context.next = 23;
78 | break;
79 | }
80 |
81 | selectedFile = event.target.files[0];
82 | file = data.open(selectedFile);
83 | _context.prev = 4;
84 | _context.next = 7;
85 | return file.rows({
86 | size: 20,
87 | keyed: true
88 | });
89 |
90 | case 7:
91 | rowStream = _context.sent;
92 | _context.next = 10;
93 | return (0, _streamToArray.default)(rowStream);
94 |
95 | case 10:
96 | file.descriptor.sample = _context.sent;
97 | _context.next = 13;
98 | return file.addSchema();
99 |
100 | case 13:
101 | _context.next = 18;
102 | break;
103 |
104 | case 15:
105 | _context.prev = 15;
106 | _context.t0 = _context["catch"](4);
107 | console.error(_context.t0);
108 |
109 | case 18:
110 | formattedSize = (0, _utils.onFormatBytes)(file.size);
111 | _context.next = 21;
112 | return file.hashSha256();
113 |
114 | case 21:
115 | hash = _context.sent;
116 |
117 | _this.props.metadataHandler(Object.assign(file.descriptor, {
118 | hash: hash
119 | }));
120 |
121 | case 23:
122 | _this.setState({
123 | selectedFile: selectedFile,
124 | loaded: 0,
125 | success: false,
126 | fileExists: false,
127 | error: false,
128 | formattedSize: formattedSize
129 | });
130 |
131 | _this.onClickHandler();
132 |
133 | case 25:
134 | case "end":
135 | return _context.stop();
136 | }
137 | }
138 | }, _callee, null, [[4, 15]]);
139 | }));
140 |
141 | return function (_x) {
142 | return _ref.apply(this, arguments);
143 | };
144 | }());
145 |
146 | _defineProperty(_assertThisInitialized(_this), "onUploadProgress", function (progressEvent) {
147 | _this.onTimeRemaining(progressEvent.loaded);
148 |
149 | _this.setState({
150 | loaded: progressEvent.loaded / progressEvent.total * 100
151 | });
152 | });
153 |
154 | _defineProperty(_assertThisInitialized(_this), "onTimeRemaining", function (progressLoaded) {
155 | var end = new Date().getTime();
156 | var duration = (end - _this.state.start) / 1000;
157 | var bps = progressLoaded / duration;
158 | var kbps = bps / 1024;
159 | var timeRemaining = (_this.state.fileSize - progressLoaded) / kbps;
160 |
161 | _this.setState({
162 | timeRemaining: timeRemaining / 1000
163 | });
164 | });
165 |
166 | _defineProperty(_assertThisInitialized(_this), "onClickHandler", /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
167 | var start, selectedFile, client, resource;
168 | return regeneratorRuntime.wrap(function _callee2$(_context2) {
169 | while (1) {
170 | switch (_context2.prev = _context2.next) {
171 | case 0:
172 | start = new Date().getTime();
173 | selectedFile = _this.state.selectedFile;
174 | client = _this.props.client;
175 | resource = data.open(selectedFile);
176 |
177 | _this.setState({
178 | fileSize: resource.size,
179 | start: start,
180 | loading: true
181 | });
182 |
183 | _this.props.handleUploadStatus({
184 | loading: true,
185 | error: false,
186 | success: false
187 | }); // Use client to upload file to the storage and track the progress
188 |
189 |
190 | client.pushBlob(resource, _this.onUploadProgress).then(function (response) {
191 | _this.setState({
192 | success: response.success,
193 | loading: false,
194 | fileExists: response.fileExists,
195 | loaded: 100
196 | });
197 |
198 | _this.props.handleUploadStatus({
199 | loading: false,
200 | success: response.success
201 | });
202 | }).catch(function (error) {
203 | _this.setState({
204 | error: true,
205 | loading: false
206 | });
207 |
208 | _this.props.handleUploadStatus({
209 | loading: false,
210 | success: false,
211 | error: true
212 | });
213 | });
214 |
215 | case 7:
216 | case "end":
217 | return _context2.stop();
218 | }
219 | }
220 | }, _callee2);
221 | })));
222 |
223 | _this.state = {
224 | datasetId: props.datasetId,
225 | selectedFile: null,
226 | fileSize: 0,
227 | formattedSize: "0 KB",
228 | start: "",
229 | loaded: 0,
230 | success: false,
231 | error: false,
232 | fileExists: false,
233 | loading: false,
234 | timeRemaining: 0
235 | };
236 | return _this;
237 | }
238 |
239 | _createClass(Upload, [{
240 | key: "render",
241 | value: function render() {
242 | var _this$state2 = this.state,
243 | success = _this$state2.success,
244 | fileExists = _this$state2.fileExists,
245 | error = _this$state2.error,
246 | timeRemaining = _this$state2.timeRemaining,
247 | selectedFile = _this$state2.selectedFile,
248 | formattedSize = _this$state2.formattedSize;
249 | return /*#__PURE__*/_react.default.createElement("div", {
250 | className: "upload-area"
251 | }, /*#__PURE__*/_react.default.createElement(_Choose.default, {
252 | onChangeHandler: this.onChangeHandler,
253 | onChangeUrl: function onChangeUrl(event) {
254 | return console.log("Get url:", event.target.value);
255 | }
256 | }), /*#__PURE__*/_react.default.createElement("div", {
257 | className: "upload-area__info"
258 | }, selectedFile && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("ul", {
259 | className: "upload-list"
260 | }, /*#__PURE__*/_react.default.createElement("li", {
261 | className: "list-item"
262 | }, /*#__PURE__*/_react.default.createElement("div", {
263 | className: "upload-list-item"
264 | }, /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("p", {
265 | className: "upload-file-name"
266 | }, selectedFile.name), /*#__PURE__*/_react.default.createElement("p", {
267 | className: "upload-file-size"
268 | }, formattedSize)), /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_ProgressBar.default, {
269 | progress: Math.round(this.state.loaded),
270 | size: 50,
271 | strokeWidth: 5,
272 | circleOneStroke: "#d9edfe",
273 | circleTwoStroke: "#7ea9e1",
274 | timeRemaining: timeRemaining
275 | }))))), /*#__PURE__*/_react.default.createElement("h2", {
276 | className: "upload-message"
277 | }, success && !fileExists && !error && "File uploaded successfully", fileExists && "File uploaded successfully", error && "Upload failed"))));
278 | }
279 | }]);
280 |
281 | return Upload;
282 | }(_react.default.Component);
283 |
284 | var _default = Upload;
285 | exports.default = _default;
--------------------------------------------------------------------------------
/dist/core.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | Object.defineProperty(exports, "Choose", {
7 | enumerable: true,
8 | get: function get() {
9 | return _Choose.default;
10 | }
11 | });
12 | Object.defineProperty(exports, "InputFile", {
13 | enumerable: true,
14 | get: function get() {
15 | return _InputFile.default;
16 | }
17 | });
18 | Object.defineProperty(exports, "InputUrl", {
19 | enumerable: true,
20 | get: function get() {
21 | return _InputUrl.default;
22 | }
23 | });
24 | Object.defineProperty(exports, "Metadata", {
25 | enumerable: true,
26 | get: function get() {
27 | return _Metadata.default;
28 | }
29 | });
30 | Object.defineProperty(exports, "ProgressBar", {
31 | enumerable: true,
32 | get: function get() {
33 | return _ProgressBar.default;
34 | }
35 | });
36 | Object.defineProperty(exports, "Switcher", {
37 | enumerable: true,
38 | get: function get() {
39 | return _Switcher.default;
40 | }
41 | });
42 | Object.defineProperty(exports, "TableSchema", {
43 | enumerable: true,
44 | get: function get() {
45 | return _TableSchema.default;
46 | }
47 | });
48 | Object.defineProperty(exports, "Upload", {
49 | enumerable: true,
50 | get: function get() {
51 | return _Upload.default;
52 | }
53 | });
54 | Object.defineProperty(exports, "encodeData", {
55 | enumerable: true,
56 | get: function get() {
57 | return _encode.default;
58 | }
59 | });
60 | Object.defineProperty(exports, "formatData", {
61 | enumerable: true,
62 | get: function get() {
63 | return _resource_formats.default;
64 | }
65 | });
66 | Object.defineProperty(exports, "licenses", {
67 | enumerable: true,
68 | get: function get() {
69 | return _licenses.default;
70 | }
71 | });
72 | Object.defineProperty(exports, "types", {
73 | enumerable: true,
74 | get: function get() {
75 | return _types.default;
76 | }
77 | });
78 |
79 | var _Choose = _interopRequireDefault(require("./components/Choose"));
80 |
81 | var _InputFile = _interopRequireDefault(require("./components/InputFile"));
82 |
83 | var _InputUrl = _interopRequireDefault(require("./components/InputUrl"));
84 |
85 | var _Metadata = _interopRequireDefault(require("./components/Metadata"));
86 |
87 | var _ProgressBar = _interopRequireDefault(require("./components/ProgressBar"));
88 |
89 | var _Switcher = _interopRequireDefault(require("./components/Switcher"));
90 |
91 | var _TableSchema = _interopRequireDefault(require("./components/TableSchema"));
92 |
93 | var _Upload = _interopRequireDefault(require("./components/Upload"));
94 |
95 | var _encode = _interopRequireDefault(require("./db/encode.json"));
96 |
97 | var _resource_formats = _interopRequireDefault(require("./db/resource_formats.json"));
98 |
99 | var _licenses = _interopRequireDefault(require("./db/licenses.json"));
100 |
101 | var _types = _interopRequireDefault(require("./db/types.json"));
102 |
103 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
--------------------------------------------------------------------------------
/dist/db/encode.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "value": "utf_8",
4 | "label": "UTF-8"
5 | },
6 | {
7 | "value": "iso_8859_1",
8 | "label": "ISO-8859-1"
9 | },
10 | {
11 | "value": "windows_1251",
12 | "label": "Windows-1251"
13 | },
14 | {
15 | "value": "windows_1252",
16 | "label": "Windows-1252"
17 | },
18 | {
19 | "value": "shift_jis",
20 | "label": "Shift JIS"
21 | },
22 | {
23 | "value": "gb2312",
24 | "label": "GB2312"
25 | },
26 | {
27 | "value": "euc_kr",
28 | "label": "EUC-KR"
29 | },
30 | {
31 | "value": "iso_8859_2",
32 | "label": "ISO-8859-2"
33 | },
34 | {
35 | "value": "windows_1250",
36 | "label": "Windows-1250"
37 | },
38 | {
39 | "value": "euc_jp",
40 | "label": "EUC-JP"
41 | },
42 | {
43 | "value": "gbk",
44 | "label": "GBK"
45 | },
46 | {
47 | "value": "big5",
48 | "label": "Big5"
49 | },
50 | {
51 | "value": "iso_8859_15",
52 | "label": "ISO-8859-15"
53 | },
54 | {
55 | "value": "windows_1256",
56 | "label": "Windows-1256"
57 | },
58 | {
59 | "value": "iso_8859_9",
60 | "label": "ISO-8859-9"
61 | }
62 | ]
--------------------------------------------------------------------------------
/dist/db/licenses.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "value": "cc-by",
4 | "label": "Creative Commons Attribution"
5 | },
6 | {
7 | "value": "cc-by-sa",
8 | "label": "Creative Commons Attribution Share-Alike"
9 | },
10 | {
11 | "value": "cc-zero",
12 | "label": "Creative Commons CCZero"
13 | },
14 | {
15 | "value": "cc-nc",
16 | "label": "Creative Commons Non-Commercial (Any)"
17 | },
18 | {
19 | "value": "gfdl",
20 | "label": "GNU Free Documentation License"
21 | },
22 | {
23 | "value": "notspecified",
24 | "label": "License not specified"
25 | },
26 | {
27 | "value": "odc-by",
28 | "label": "Open Data Commons Attribution License"
29 | },
30 | {
31 | "value": "odc-odbl",
32 | "label": "Open Data Commons Open Database License (ODbL)"
33 | },
34 | {
35 | "value": "odc-pddl",
36 | "label": "Open Data Commons Public Domain Dedication and License (PDDL)"
37 | },
38 | {
39 | "value": "other-at",
40 | "label": "Other (Attribution)"
41 | },
42 | {
43 | "value": "other-nc",
44 | "label": "Other (Non-Commercial)"
45 | },
46 | {
47 | "value": "other-closed",
48 | "label": "Other (Not Open)"
49 | },
50 | {
51 | "value": "other-open",
52 | "label": "Other (Open)"
53 | },
54 | {
55 | "value": "other-pd",
56 | "label": "Other (Public Domain)"
57 | },
58 | {
59 | "value": "uk-ogl",
60 | "label": "UK Open Government Licence (OGL)"
61 | }
62 | ]
--------------------------------------------------------------------------------
/dist/db/resource_formats.json:
--------------------------------------------------------------------------------
1 | [
2 | ["PPTX", "Powerpoint OOXML Presentation", "application/vnd.openxmlformats-officedocument.presentationml.presentation", []],
3 | ["EXE", "Windows Executable Program", "application/x-msdownload", []],
4 | ["DOC", "Word Document", "application/msword", []],
5 | ["KML", "KML File", "application/vnd.google-earth.kml+xml", []],
6 | ["XLS", "Excel Document", "application/vnd.ms-excel", ["Excel", "application/msexcel", "application/x-msexcel", "application/x-ms-excel", "application/x-excel", "application/x-dos_ms_excel", "application/xls", "application/x-xls"]],
7 | ["WCS", "Web Coverage Service", "wcs", []],
8 | ["JS", "JavaScript", "application/x-javascript", []],
9 | ["MDB", "Access Database", "application/x-msaccess", []],
10 | ["NetCDF", "NetCDF File", "application/netcdf", []],
11 | ["ArcGIS Map Service", "ArcGIS Map Service", "ArcGIS Map Service", ["arcgis map service"]],
12 | ["TSV", "Tab Separated Values File", "text/tab-separated-values", ["text/tsv"]],
13 | ["WFS", "Web Feature Service", null, []],
14 | ["WMTS", "Web Map Tile Service", null, []],
15 | ["ArcGIS Online Map", "ArcGIS Online Map", "ArcGIS Online Map", ["web map application"]],
16 | ["Perl", "Perl Script", "text/x-perl", []],
17 | ["KMZ", "KMZ File", "application/vnd.google-earth.kmz+xml", ["application/vnd.google-earth.kmz"]],
18 | ["OWL", "Web Ontology Language", "application/owl+xml", []],
19 | ["N3", "N3 Triples", "application/x-n3", []],
20 | ["ZIP", "Zip File", "application/zip", ["zip", "http://purl.org/NET/mediatypes/application/zip"]],
21 | ["GZ", "Gzip File", "application/gzip", ["application/x-gzip"]],
22 | ["QGIS", "QGIS File", "application/x-qgis", []],
23 | ["ODS", "OpenDocument Spreadsheet", "application/vnd.oasis.opendocument.spreadsheet", []],
24 | ["ODT", "OpenDocument Text", "application/vnd.oasis.opendocument.text", []],
25 | ["JSON", "JavaScript Object Notation", "application/json", []],
26 | ["BMP", "Bitmap Image File", "image/x-ms-bmp", []],
27 | ["HTML", "Web Page", "text/html", ["htm", "http://purl.org/net/mediatypes/text/html"]],
28 | ["RAR", "RAR Compressed File", "application/rar", []],
29 | ["TIFF", "TIFF Image File", "image/tiff", []],
30 | ["ODB", "OpenDocument Database", "application/vnd.oasis.opendocument.database", []],
31 | ["TXT", "Text File", "text/plain", []],
32 | ["DCR", "Adobe Shockwave format", "application/x-director", []],
33 | ["ODF", "OpenDocument Math Formula", "application/vnd.oasis.opendocument.formula", []],
34 | ["ODG", "OpenDocument Image", "application/vnd.oasis.opendocument.graphics", []],
35 | ["XML", "XML File", "application/xml", ["text/xml", "http://purl.org/net/mediatypes/application/xml"]],
36 | ["XLSX", "Excel OOXML Spreadsheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", []],
37 | ["DOCX", "Word OOXML Document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []],
38 | ["BIN", "Binary Data", "application/octet-stream", ["bin"]],
39 | ["XSLT", "Extensible Stylesheet Language Transformations", "application/xslt+xml", []],
40 | ["WMS", "Web Mapping Service", "WMS", ["wms"]],
41 | ["SVG", "SVG vector image", "image/svg+xml", ["svg"]],
42 | ["PPT", "Powerpoint Presentation", "application/vnd.ms-powerpoint", []],
43 | ["ODP", "OpenDocument Presentation", "application/vnd.oasis.opendocument.presentation", []],
44 | ["JPEG", "JPG Image File", "image/jpeg", ["jpeg", "jpg"]],
45 | ["SPARQL", "SPARQL end-point", "application/sparql-results+xml", []],
46 | ["GIF", "GIF Image File", "image/gif", []],
47 | ["RDF", "RDF", "application/rdf+xml", ["rdf/xml"]],
48 | ["E00", " ARC/INFO interchange file format", "application/x-e00", []],
49 | ["PDF", "PDF File", "application/pdf", []],
50 | ["CSV", "Comma Separated Values File", "text/csv", ["text/comma-separated-values"]],
51 | ["ODC", "OpenDocument Chart", "application/vnd.oasis.opendocument.chart", []],
52 | ["Atom Feed", "Atom Feed", "application/atom+xml", []],
53 | ["MrSID", "MrSID", "image/x-mrsid", []],
54 | ["ArcGIS Map Preview", "ArcGIS Map Preview", "ArcGIS Map Preview", ["arcgis map preview"]],
55 | ["XYZ", "XYZ Chemical File", "chemical/x-xyz", []],
56 | ["MOP", "MOPAC Input format", "chemical/x-mopac-input", []],
57 | ["Esri REST", "Esri Rest API Endpoint", "Esri REST", ["arcgis_rest"]],
58 | ["dBase", "dBase Database", "application/x-dbf", ["dbf"]],
59 | ["MXD", "ESRI ArcGIS project file", "application/x-mxd", []],
60 | ["TAR", "TAR Compressed File", "application/x-tar", []],
61 | ["PNG", "PNG Image File", "image/png", []],
62 | ["RSS", "RSS feed", "application/rss+xml", []],
63 | ["GeoJSON", "Geographic JavaScript Object Notation", "application/geo+json", ["geojson"]],
64 | ["SHP", "Shapefile", null, ["esri shapefile"]],
65 | ["TORRENT", "Torrent", "application/x-bittorrent", ["bittorrent"]],
66 | ["ICS", "iCalendar", "text/calendar", ["ifb", "iCal"]],
67 | ["GTFS", "General Transit Feed Specification", null, []],
68 | ["XLSB", "Excel OOXML Spreadsheet - Binary Workbook", "application/vnd.ms-excel.sheet.binary.macroEnabled.12", []],
69 | ["XLSM", "Excel OOXML Spreadsheet - Macro-Enabled", "application/vnd.ms-excel.sheet.macroEnabled.12", []],
70 | ["XLTM", "Excel OOXML Spreadsheet - Macro-Enabled Template", "application/vnd.ms-excel.template.macroEnabled.12", []],
71 | ["XLAM", "Excel OOXML Spreadsheet - Macro-Enabled Add-In", "application/vnd.ms-excel.addin.macroEnabled.12", []],
72 | ["DOTX", "Word OOXML - Template", "application/vnd.openxmlformats-officedocument.wordprocessingml.template", []],
73 | ["DOCM", "Word OOXML - Macro Enabled", "application/vnd.ms-word.document.macroEnabled.12", []],
74 | ["DOTM", "Word OOXML Template - Macro Enabled", "application/vnd.ms-word.template.macroEnabled.12", []],
75 | ["XLTX", "Excel OOXML Spreadsheet - Template", "application/vnd.openxmlformats-officedocument.spreadsheetml.template", []],
76 | ["POTX", "PowerPoint OOXML Presentation - Template", "application/vnd.openxmlformats-officedocument.presentationml.template", []],
77 | ["PPSX", "PowerPoint Open XML - Slide Show", "application/vnd.openxmlformats-officedocument.presentationml.slideshow", []],
78 | ["PPAM", "PowerPoint Open XML - Macro-Enabled Add-In", "application/vnd.ms-powerpoint.addin.macroEnabled.12", []],
79 | ["PPTM", "PowerPoint Open XML - Macro-Enabled", "application/vnd.ms-powerpoint.presentation.macroEnabled.12", []],
80 | ["POTM", "PowerPoint Open XML - Macro-Enabled Template", "application/vnd.ms-powerpoint.template.macroEnabled.12", []],
81 | ["PPSM", "PowerPoint Open XML - Macro-Enabled Slide Show", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", []]
82 | ]
--------------------------------------------------------------------------------
/dist/db/types.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": [
3 | "string",
4 | "number",
5 | "integer",
6 | "boolean",
7 | "object",
8 | "array",
9 | "date",
10 | "time",
11 | "datetime",
12 | "year",
13 | "yearmonth",
14 | "duration",
15 | "geopoint",
16 | "geojson",
17 | "any"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | Object.defineProperty(exports, "ResourceEditor", {
9 | enumerable: true,
10 | get: function get() {
11 | return _App.ResourceEditor;
12 | }
13 | });
14 |
15 | var _react = _interopRequireDefault(require("react"));
16 |
17 | var _reactDom = _interopRequireDefault(require("react-dom"));
18 |
19 | var _App = _interopRequireWildcard(require("./App"));
20 |
21 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
22 |
23 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
24 |
25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26 |
27 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
28 |
29 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
30 |
31 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
32 |
33 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
34 |
35 | function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
36 |
37 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
38 |
39 | // Mount the ResourceEditor app explicitly
40 | // (make sure you call the function after it's loaded)
41 | function mountResourceEditorApp() {
42 | var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['root', {
43 | authToken: null,
44 | api: null,
45 | lfs: null,
46 | organizationId: null,
47 | datasetId: null,
48 | resourceId: null
49 | }, {}],
50 | _ref2 = _slicedToArray(_ref, 3),
51 | elementId = _ref2[0],
52 | config = _ref2[1],
53 | resource = _ref2[2];
54 |
55 | _reactDom.default.render( /*#__PURE__*/_react.default.createElement(_react.default.StrictMode, null, /*#__PURE__*/_react.default.createElement(_App.default, {
56 | config: config,
57 | resource: resource
58 | })), document.getElementById(elementId));
59 | }
60 |
61 | ; // Automatically mount the app if an element with id='ResourceEditor' exists
62 |
63 | var element = document.getElementById('ResourceEditor');
64 |
65 | if (element) {
66 | var config = {
67 | datasetId: element.getAttribute('data-dataset-id'),
68 | api: element.getAttribute('data-api'),
69 | lfs: element.getAttribute('data-lfs'),
70 | authToken: element.getAttribute('data-auth-token'),
71 | organizationId: element.getAttribute('data-organization-id'),
72 | resourceId: element.getAttribute('data-resource-id')
73 | };
74 |
75 | _reactDom.default.render( /*#__PURE__*/_react.default.createElement(_react.default.StrictMode, null, /*#__PURE__*/_react.default.createElement(_App.default, {
76 | config: config,
77 | resource: element.getAttribute('data-resource')
78 | })), element);
79 | }
--------------------------------------------------------------------------------
/dist/setupTests.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require("@testing-library/jest-dom/extend-expect");
4 |
5 | var _enzyme = require("enzyme");
6 |
7 | var _enzymeAdapterReact = _interopRequireDefault(require("enzyme-adapter-react-16"));
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
12 | // allows you to do things like:
13 | // expect(element).toHaveTextContent(/react/i)
14 | // learn more: https://github.com/testing-library/jest-dom
15 | (0, _enzyme.configure)({
16 | adapter: new _enzymeAdapterReact.default()
17 | });
--------------------------------------------------------------------------------
/dist/utils/Utils.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _react = _interopRequireDefault(require("react"));
4 |
5 | var _reactDom = _interopRequireDefault(require("react-dom"));
6 |
7 | var _index = require("./index");
8 |
9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10 |
11 | describe("Utils", function () {
12 | it('format size in Bytes, KB, MB, GB', function () {
13 | expect((0, _index.onFormatBytes)(100)).toEqual('100 Bytes');
14 | expect((0, _index.onFormatBytes)(1000)).toEqual('1 KB');
15 | expect((0, _index.onFormatBytes)(1222222)).toEqual('1.2 MB');
16 | expect((0, _index.onFormatBytes)(1222222222)).toEqual('1.2 GB');
17 | });
18 | it('format title', function () {
19 | expect((0, _index.onFormatTitle)("sample-data.csv")).toEqual('sample data');
20 | expect((0, _index.onFormatTitle)("sample_data.csv")).toEqual('sample data');
21 | expect((0, _index.onFormatTitle)("sample-data_csv.csv")).toEqual('sample data csv');
22 | expect((0, _index.onFormatTitle)("sampleData.csv")).toEqual('sampleData');
23 | });
24 | it('get file extension', function () {
25 | expect((0, _index.getFileExtension)("sample.csv")).toEqual('csv');
26 | expect((0, _index.getFileExtension)("sample.html")).toEqual('html');
27 | expect((0, _index.getFileExtension)("sample.xls")).toEqual('xls');
28 | expect((0, _index.getFileExtension)("sampleData.doc")).toEqual('doc');
29 | });
30 | it('format name', function () {
31 | expect((0, _index.onFormatName)("sample.csv")).toEqual('sample');
32 | expect((0, _index.onFormatName)("sample_data.html")).toEqual('sample_data');
33 | expect((0, _index.onFormatName)("sample-data.xls")).toEqual('sample-data');
34 | expect((0, _index.onFormatName)("sample-Data.doc")).toEqual('sample-Data');
35 | });
36 | it('remove hyphen from uuid', function () {
37 | expect((0, _index.removeHyphen)("fd77e419-32ae-4025-8d14-890343b605a3")).toEqual('fd77e41932ae40258d14890343b605a3');
38 | expect((0, _index.removeHyphen)("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d")).toEqual('9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d');
39 | expect((0, _index.removeHyphen)("should not broken without hyphen")).toEqual('should not broken without hyphen');
40 | });
41 | });
--------------------------------------------------------------------------------
/dist/utils/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.removeHyphen = exports.onFormatBytes = exports.onFormatName = exports.onFormatTitle = exports.getFileExtension = void 0;
7 |
8 | var getFileExtension = function getFileExtension(filename) {
9 | return /[.]/.exec(filename) ? /[^.]+$/.exec(filename)[0] : undefined;
10 | };
11 |
12 | exports.getFileExtension = getFileExtension;
13 |
14 | var onFormatTitle = function onFormatTitle(name) {
15 | return name.replace(/\.[^/.]+$/, "").replace(/_/g, " ").replace(/-/g, " ");
16 | };
17 |
18 | exports.onFormatTitle = onFormatTitle;
19 |
20 | var onFormatName = function onFormatName(name) {
21 | return name.replace(/\.[^/.]+$/, "");
22 | };
23 |
24 | exports.onFormatName = onFormatName;
25 |
26 | var onFormatBytes = function onFormatBytes(bytes) {
27 | var decimals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
28 | if (bytes === 0) return "0 Bytes";
29 | var k = 1000;
30 | var dm = decimals < 0 ? 0 : decimals;
31 | var sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
32 | var i = Math.floor(Math.log(bytes) / Math.log(k));
33 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
34 | };
35 |
36 | exports.onFormatBytes = onFormatBytes;
37 |
38 | var removeHyphen = function removeHyphen(id) {
39 | return id.replace(/-/g, "");
40 | };
41 |
42 | exports.removeHyphen = removeHyphen;
--------------------------------------------------------------------------------
/examples/basic/assets/after-css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/basic/assets/after-css.png
--------------------------------------------------------------------------------
/examples/basic/assets/before-css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/basic/assets/before-css.png
--------------------------------------------------------------------------------
/examples/basic/assets/table-schema-example.md:
--------------------------------------------------------------------------------
1 | ## Adding TableSchema Component to your react application
2 |
3 | In this example, you'll learn how to use two Datapub components in your react application. By the end of this example, you'll have a working application like the one below:
4 |
5 |
6 |
7 | ### Pre-requisite
8 |
9 | - Have Node and Npm/Yarn installed
10 | - Have create-react-app
11 |
12 | ### Set-up
13 | In a new folder of your work space, open a terminal and create a new react application:
14 | ```bash
15 | create-react-app datapub-extend
16 | ```
17 | Change directory into datapub-extend and run the application to ensure it was created successfully:
18 | ```bash
19 | cd datapub-extend
20 | yarn start
21 | ```
22 | If you can see the react logo in your browser, then everything works fine. In the next section, you'll install Datapub and import some components for use.
23 |
24 | Open `datapub-extend` in your code editor, and add the following to your dependencies in `package.json`:
25 |
26 | ```json
27 | ...
28 | "datapub": "git+https://github.com/datopian/datapub.git",
29 | "ckan-client": "git+https://github.com/datopian/ckan-client-js.git",
30 |
31 | ...
32 | ```
33 |
34 | Run `yarn` or `npm install` in your terminal to install Datapub. This installs the latest version of datapub and ckan-client from Github.
35 |
36 | In your App.js script in `src` folder, delete the auto generated contents and add the import statements below:
37 |
38 | ```javascript
39 | import React from 'react';
40 | import { Client } from "ckan-client";
41 | import PropTypes from "prop-types";
42 | import frictionlessCkanMapper from "frictionless-ckan-mapper-js";
43 | import { Upload, Metadata } from "datapub";
44 | import './App.css';
45 | ```
46 | You're are creating a client for accessing CKAN using the ckan-client package, importing the frictionless-ckan-mapper for mapping schema, and most importantly importing two components (upload and metadata) from datapub.
47 |
48 | __Note:__ Available components that you can reuse from datapub are:
49 | - Metadata
50 | - Choose
51 | - InputFile
52 | - InputUrl
53 | - ProgressBar
54 | - Upload
55 | - TableSchema
56 |
57 | Next, you can add your custom `ResourceEditor`. Copy and paste the code below just in your App.js script:
58 |
59 | ```javascript
60 |
61 | export class ResourceEditor extends React.Component {
62 | constructor(props) {
63 | super(props);
64 | this.state = {
65 | datasetId: this.props.config.datasetId,
66 | resourceId: "",
67 | resource: this.props.resource || {},
68 | ui: {
69 | fileOrLink: "",
70 | uploadComplete: undefined,
71 | success: false,
72 | error: false,
73 | loading: false,
74 | },
75 | client: null,
76 | isResourceEdit: false,
77 | };
78 | this.metadataHandler = this.metadataHandler.bind(this);
79 | }
80 |
81 | async componentDidMount() {
82 | const { config } = this.props;
83 | const {
84 | authToken,
85 | api,
86 | lfs,
87 | organizationId,
88 | datasetId,
89 | resourceId,
90 | } = config;
91 |
92 | const client = new Client(
93 | `${authToken}`,
94 | `${organizationId}`,
95 | `${datasetId}`,
96 | `${api}`,
97 | `${lfs}`
98 | );
99 |
100 | //Check if the user is editing resource
101 | if (resourceId) {
102 | const resource = await client.action("resource_show", { id: resourceId });
103 | const resourceSchema = await client.action("resource_schema_show", {
104 | id: resourceId,
105 | });
106 | const resourceSample = await client.action("resource_sample_show", {
107 | id: resourceId,
108 | });
109 |
110 | let resourceCopy = resource.result;
111 | let sampleCopy = [];
112 |
113 | try {
114 | // push the values to an array
115 | for (const property in resourceSample.result) {
116 | sampleCopy.push(resourceSample.result[property]);
117 | }
118 | // push sample as an array to be able to render in tableschema component
119 | resourceCopy.sample = sampleCopy;
120 | resourceCopy.schema = resourceSchema.result;
121 | } catch (e) {
122 | console.error(e);
123 | //generate empty values not to break the tableschema component
124 | resourceCopy.schema = { fields: [] };
125 | resourceCopy.sample = [];
126 | }
127 |
128 | return this.setState({
129 | client,
130 | resourceId,
131 | resource: resourceCopy,
132 | isResourceEdit: true,
133 | });
134 | }
135 |
136 | this.setState({ client });
137 | }
138 |
139 | metadataHandler(resource) {
140 | this.setState({
141 | resource,
142 | });
143 | }
144 |
145 | handleChangeMetadata = (event) => {
146 | const target = event.target;
147 | const value = target.value;
148 | const name = target.name;
149 | let resourceCopy = this.state.resource;
150 | resourceCopy[name] = value;
151 |
152 | this.setState({
153 | resource: resourceCopy,
154 | });
155 | };
156 |
157 | handleSubmitMetadata = async () => {
158 | const { resource, client } = this.state;
159 |
160 | await this.createResource(resource);
161 | const isResourceCreate = true;
162 | if (isResourceCreate) {
163 | const datasetMetadata = await client.action("package_show", {
164 | id: this.state.datasetId,
165 | });
166 | let result = datasetMetadata.result;
167 |
168 | if (result.state === "draft") {
169 | result.state = "active";
170 | await client.action("package_update", result);
171 | }
172 | }
173 |
174 | // Redirect to dataset page
175 | return (window.location.href = `/dataset/${this.state.datasetId}`);
176 | };
177 |
178 | createResource = async (resource) => {
179 | const { client } = this.state;
180 | const { config } = this.props;
181 | const { organizationId, datasetId, resourceId } = config;
182 |
183 | const ckanResource = frictionlessCkanMapper.resourceFrictionlessToCkan(
184 | resource
185 | );
186 |
187 | //create a valid format from sample
188 | let data = { ...ckanResource.sample };
189 | //delete sample because is an invalid format
190 | delete ckanResource.sample;
191 |
192 | // create a copy from ckanResource to add package_id, name, url, sha256,size, lfs_prefix, url, url_type
193 | // without this properties ckan-blob-storage doesn't work properly
194 | let ckanResourceCopy = {
195 | ...ckanResource,
196 | package_id: this.state.datasetId,
197 | name: resource.name || resource.title,
198 | sha256: resource.hash,
199 | size: resource.size,
200 | lfs_prefix: `${organizationId}/${datasetId}`,
201 | url: resource.name,
202 | url_type: "upload",
203 | sample: data,
204 | };
205 |
206 | //Check if the user is editing resource, call resource_update and redirect to the dataset page
207 | if (resourceId) {
208 | ckanResourceCopy = {
209 | ...ckanResourceCopy,
210 | id: resourceId,
211 | };
212 | await client.action("resource_update", ckanResourceCopy);
213 |
214 | return (window.location.href = `/dataset/${datasetId}`);
215 | }
216 | await client
217 | .action("resource_create", ckanResourceCopy)
218 | .then((response) => {
219 | this.onChangeResourceId(response.result.id);
220 | });
221 | };
222 |
223 | deleteResource = async () => {
224 | const { resource, client, datasetId } = this.state;
225 | if (window.confirm("Are you sure to delete this resource?")) {
226 | await client.action("resource_delete", { id: resource.id });
227 |
228 | return (window.location.href = `/dataset/${datasetId}`);
229 | }
230 | };
231 |
232 | handleUploadStatus = (status) => {
233 | const { ui } = this.state;
234 | const newUiState = {
235 | ...ui,
236 | success: status.success,
237 | error: status.error,
238 | loading: status.loading,
239 | };
240 |
241 | this.setState({ ui: newUiState });
242 | };
243 |
244 | onChangeResourceId = (resourceId) => {
245 | this.setState({ resourceId });
246 | };
247 | }
248 | ```
249 |
250 | # TODO: Some brief explanation on the ResourceEditor
251 |
252 | Next, you'll add two Datapub components (Upload and Metadata). At the end of `ResourceEditor` class, add the react render method, and initialize the Datapub components you have imported as shown below:
253 |
254 | ```javascript
255 |
256 | ...
257 | ...
258 |
259 | render() {
260 | const { success } = this.state.ui;
261 |
262 | return (
263 |
312 | );
313 | }
314 | ```
315 |
316 | The Upload component requires 5 properties:
317 | - client
318 | - resource
319 | - metadataHandler
320 | - datasetId
321 | - handleUploadStatus
322 | - onChangeResourceId
323 |
324 | While the Metadata componet requires two properties
325 | - metadata
326 | - handleChange
327 |
328 |
329 | Finally, add this last bit of code to define your default configuration for the ResourceEditor before exporting the component.
330 |
331 | ```javascript
332 |
333 | ResourceEditor.defaultProps = {
334 | config: {
335 | authToken: "be270cae-1c77-4853-b8c1-30b6cf5e9878",
336 | api: "http://127.0.0.1:5000",
337 | lfs: "http://localhost:9419",
338 | organizationId: "myorg",
339 | datasetId: "sample_1",
340 | },
341 | };
342 |
343 | ResourceEditor.propTypes = {
344 | config: PropTypes.object.isRequired,
345 | };
346 |
347 | export default ResourceEditor;
348 | ```
349 |
350 | You can see the full App.js script [here](./code/reusable_datapub_basic/src/App.js)
351 |
352 | Next, open your `index.js` script, delete the existing code and paste the one below:
353 |
354 | ```javascript
355 | import React from 'react';
356 | import ReactDOM from 'react-dom';
357 | import App from './App';
358 |
359 |
360 | const element = document.getElementById('ResourceEditor');
361 | if (element) {
362 | const config = {
363 | datasetId: element.getAttribute('data-dataset-id'),
364 | api: element.getAttribute('data-api'),
365 | lfs: element.getAttribute('data-lfs'),
366 | authToken: element.getAttribute('data-auth-token'),
367 | organizationId: element.getAttribute('data-organization-id'),
368 | resourceId: element.getAttribute('data-resource-id')
369 | }
370 |
371 | ReactDOM.render(
372 |
373 |
377 | ,
378 | element
379 | );
380 | }
381 |
382 |
383 | export { ResourceEditor } from './App';
384 | ```
385 |
386 | This automatically mounts the ResourceEditor component in the your homepage if an element with the ID `ResourceEditor` exists.
387 |
388 | The `ResourceEditor` element does not exist since you've not created one, so open your index.html file in the public folder, delete the:
389 |
390 | ```html
391 |
392 | ```
393 | and add:
394 |
395 | ```html
396 |
402 |
403 | ```
404 |
405 | The properties `data-api`, `data-lfs`, `data-auth-token`, `data-dataset-id`, `data-organization-id` are for connecting to your Ckan instance using ckan-client.
406 |
407 | Now you're all set, you can start your application by running:
408 | ```bash
409 | yarn start
410 | ```
411 |
412 | Your browser should open at port 3000 if its available and display something similar to the page below:
413 |
414 |
415 |
416 | You can customize styling, or add some pre-written ones. Copy the CSS style [here](./code/reusable_datapub_basic/src/App.css) and add to your App.css.
417 |
418 | Your app should now display like:
419 |
420 |
421 |
422 | Try uploading a file, and the Metadata will displayed. This proves that you have been able to successfully reuse the `Upload` and `Metadata` components from `Datapub`.
423 |
424 |
425 |
426 | __Note:__ The upload only failed because you do not have a Ckan client running.
427 |
428 | See the full code for this example project [here](./code/reusable_datapub_basic)
429 |
430 | ## See Also:
--------------------------------------------------------------------------------
/examples/basic/assets/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/basic/assets/upload.png
--------------------------------------------------------------------------------
/examples/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datapub-extend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "react": "^16.14.0",
10 | "react-dom": "^16.14.0",
11 | "react-scripts": "3.4.3",
12 | "datapub": "git+https://github.com/datopian/datapub.git",
13 | "ckanClient": "git+https://github.com/datopian/ckan-client-js.git"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": {
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ],
30 | "development": [
31 | "last 1 chrome version",
32 | "last 1 firefox version",
33 | "last 1 safari version"
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/basic/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/basic/public/favicon.ico
--------------------------------------------------------------------------------
/examples/basic/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/basic/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/basic/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/table-schema/assets/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/table-schema/assets/app.png
--------------------------------------------------------------------------------
/examples/table-schema/assets/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/examples/table-schema/assets/table.png
--------------------------------------------------------------------------------
/examples/table-schema/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "table-schema-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "datapub": "0.2.2",
10 | "ckanClient": "git+https://github.com/datopian/ckan-client-js.git",
11 | "react": "^16.14.0",
12 | "react-dom": "^16.14.0",
13 | "react-scripts": "3.4.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 |
21 | },
22 | "eslintConfig": {
23 | "extends": "react-app"
24 | },
25 | "browserslist": {
26 | "production": [
27 | ">0.2%",
28 | "not dead",
29 | "not op_mini all"
30 | ],
31 | "development": [
32 | "last 1 chrome version",
33 | "last 1 firefox version",
34 | "last 1 safari version"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/table-schema/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/table-schema/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/table-schema/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/table-schema/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | .App {
16 | text-align: center;
17 | min-height: 100vh;
18 | color: #222;
19 | }
20 |
21 | .upload-wrapper {
22 | width: 100%;
23 | min-width: 360px;
24 | max-width: 960px;
25 | margin: 0 auto;
26 | background-color: #fff;
27 | padding: 32px 24px;
28 | border-radius: 12px;
29 | min-height: 60%;
30 | display: grid;
31 | grid-template-columns: 1fr 1fr;
32 | grid-template-rows: auto 300px auto auto;
33 | grid-template-areas:
34 | "header header"
35 | "uploadArea uploadArea"
36 | "editArea editArea";
37 | row-gap: 20px;
38 | column-gap: 20px;
39 | }
40 |
41 | .upload-header {
42 | grid-area: header;
43 | border-bottom: 1px solid #e3ebff;
44 | }
45 |
46 | .upload-header__title {
47 | color: #3f4656;
48 | font-weight: 400;
49 | }
50 |
51 | .upload-area {
52 | grid-area: uploadArea;
53 | display: grid;
54 | grid-template-columns: 40% auto;
55 | grid-template-rows: 1fr 1fr;
56 | grid-template-areas: "drop info";
57 | align-items: center;
58 | }
59 |
60 | .upload-area__info {
61 | grid-area: info;
62 | padding-top: 20px;
63 | }
64 |
65 | .upload-list {
66 | padding: 0;
67 | }
68 |
69 | .list-item {
70 | display: flex;
71 | }
72 |
73 | .upload-list-item {
74 | margin: 0 auto;
75 | background-color: #fff;
76 | padding: 8px 24px;
77 | list-style: none;
78 | max-width: 480px;
79 | width: 85%;
80 | border-top-left-radius: 8px;
81 | border-bottom-left-radius: 8px;
82 | color: #222;
83 | display: flex;
84 | align-items: center;
85 | justify-content: space-between;
86 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
87 | }
88 |
89 | .upload-file-name {
90 | max-width: 19ch;
91 | white-space: nowrap;
92 | overflow: hidden;
93 | text-overflow: ellipsis;
94 | color: #3f4656;
95 | }
96 |
97 | .upload-file-size {
98 | text-align: left;
99 | font-size: 0.75rem;
100 | color: lightslategrey;
101 | }
102 |
103 | .upload-message {
104 | color: #3f4656;
105 | }
106 |
107 | .upload-switcher {
108 | grid-area: switcher;
109 | }
110 |
111 | .upload-edit-area {
112 | grid-area: editArea;
113 | }
114 |
115 | .btn {
116 | max-width: 220px;
117 | margin: 2rem;
118 | border-radius: 5px;
119 | padding: 10px 25px;
120 | height: 45px;
121 | font-size: 1.2rem;
122 | color: #fff;
123 | background-color: #00A7E1;
124 | outline: none;
125 | border: none;
126 | cursor: pointer;
127 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
128 | }
129 |
130 | .btn:disabled {
131 | background: #e3e9ed !important;
132 | cursor: default;
133 | }
134 |
135 | .btn:hover {
136 | background-color: #009ace;
137 | }
138 |
139 | .btn-delete {
140 | margin: 2rem;
141 | border-radius: 5px;
142 | padding: 10px 25px;
143 | height: 45px;
144 | font-size: 1.2rem;
145 | color: #fff;
146 | outline: none;
147 | border: none;
148 | cursor: pointer;
149 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
150 | background-color: #F11301;
151 | }
152 |
153 | .btn-delete :disabled {
154 | background: #e3e9ed !important;
155 | cursor: default;
156 | }
157 |
158 | .btn-delete:hover {
159 | background-color: #C70E00;
160 | }
161 |
162 | .resource-edit-actions {
163 | display: flex;
164 | justify-content: space-between;
165 | }
--------------------------------------------------------------------------------
/examples/table-schema/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Client } from "ckanClient";
3 | import PropTypes from "prop-types";
4 | import frictionlessCkanMapper from "frictionless-ckan-mapper-js";
5 | import { Upload, Metadata, TableSchema } from "datapub";
6 |
7 | import "./App.css";
8 |
9 | export class ResourceEditor extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | datasetId: this.props.config.datasetId,
14 | resourceId: "",
15 | resource: this.props.resource || {},
16 | ui: {
17 | fileOrLink: "",
18 | uploadComplete: undefined,
19 | success: false,
20 | error: false,
21 | loading: false,
22 | },
23 | client: null,
24 | isResourceEdit: false,
25 | };
26 | this.metadataHandler = this.metadataHandler.bind(this);
27 | }
28 |
29 | async componentDidMount() {
30 | const { config } = this.props;
31 | const {
32 | authToken,
33 | api,
34 | lfs,
35 | organizationId,
36 | datasetId,
37 | resourceId,
38 | } = config;
39 |
40 | const client = new Client(
41 | `${authToken}`,
42 | `${organizationId}`,
43 | `${datasetId}`,
44 | `${api}`,
45 | `${lfs}`
46 | );
47 |
48 | //Check if the user is editing resource
49 | if (resourceId) {
50 | const resource = await client.action("resource_show", { id: resourceId });
51 | const resourceSchema = await client.action("resource_schema_show", {
52 | id: resourceId,
53 | });
54 | const resourceSample = await client.action("resource_sample_show", {
55 | id: resourceId,
56 | });
57 |
58 | let resourceCopy = resource.result;
59 | let sampleCopy = [];
60 |
61 | try {
62 | // push the values to an array
63 | for (const property in resourceSample.result) {
64 | sampleCopy.push(resourceSample.result[property]);
65 | }
66 | // push sample as an array to be able to render in tableschema component
67 | resourceCopy.sample = sampleCopy;
68 | resourceCopy.schema = resourceSchema.result;
69 | } catch (e) {
70 | console.error(e);
71 | //generate empty values not to break the tableschema component
72 | resourceCopy.schema = { fields: [] };
73 | resourceCopy.sample = [];
74 | }
75 |
76 | return this.setState({
77 | client,
78 | resourceId,
79 | resource: resourceCopy,
80 | isResourceEdit: true,
81 | });
82 | }
83 |
84 | this.setState({ client });
85 | }
86 |
87 | metadataHandler(resource) {
88 | this.setState({
89 | resource,
90 | });
91 | }
92 |
93 | handleChangeMetadata = (event) => {
94 | const target = event.target;
95 | const value = target.value;
96 | const name = target.name;
97 | let resourceCopy = this.state.resource;
98 | resourceCopy[name] = value;
99 |
100 | this.setState({
101 | resource: resourceCopy,
102 | });
103 | };
104 |
105 | handleSubmitMetadata = async () => {
106 | const { resource, client } = this.state;
107 |
108 | await this.createResource(resource);
109 | const isResourceCreate = true;
110 | if (isResourceCreate) {
111 | const datasetMetadata = await client.action("package_show", {
112 | id: this.state.datasetId,
113 | });
114 | let result = datasetMetadata.result;
115 |
116 | if (result.state === "draft") {
117 | result.state = "active";
118 | await client.action("package_update", result);
119 | }
120 | }
121 |
122 | // Redirect to dataset page
123 | return (window.location.href = `/dataset/${this.state.datasetId}`);
124 | };
125 |
126 | createResource = async (resource) => {
127 | const { client } = this.state;
128 | const { config } = this.props;
129 | const { organizationId, datasetId, resourceId } = config;
130 |
131 | const ckanResource = frictionlessCkanMapper.resourceFrictionlessToCkan(
132 | resource
133 | );
134 |
135 | //create a valid format from sample
136 | let data = { ...ckanResource.sample };
137 | //delete sample because is an invalid format
138 | delete ckanResource.sample;
139 |
140 | // create a copy from ckanResource to add package_id, name, url, sha256,size, lfs_prefix, url, url_type
141 | // without this properties ckan-blob-storage doesn't work properly
142 | let ckanResourceCopy = {
143 | ...ckanResource,
144 | package_id: this.state.datasetId,
145 | name: resource.name || resource.title,
146 | sha256: resource.hash,
147 | size: resource.size,
148 | lfs_prefix: `${organizationId}/${datasetId}`,
149 | url: resource.name,
150 | url_type: "upload",
151 | sample: data,
152 | };
153 |
154 | //Check if the user is editing resource, call resource_update and redirect to the dataset page
155 | if (resourceId) {
156 | ckanResourceCopy = {
157 | ...ckanResourceCopy,
158 | id: resourceId,
159 | };
160 | await client.action("resource_update", ckanResourceCopy);
161 |
162 | return (window.location.href = `/dataset/${datasetId}`);
163 | }
164 | await client
165 | .action("resource_create", ckanResourceCopy)
166 | .then((response) => {
167 | this.onChangeResourceId(response.result.id);
168 | });
169 | };
170 |
171 | deleteResource = async () => {
172 | const { resource, client, datasetId } = this.state;
173 | if (window.confirm("Are you sure to delete this resource?")) {
174 | await client.action("resource_delete", { id: resource.id });
175 |
176 | return (window.location.href = `/dataset/${datasetId}`);
177 | }
178 | };
179 |
180 | handleUploadStatus = (status) => {
181 | const { ui } = this.state;
182 | const newUiState = {
183 | ...ui,
184 | success: status.success,
185 | error: status.error,
186 | loading: status.loading,
187 | };
188 |
189 | this.setState({ ui: newUiState });
190 | };
191 |
192 | onChangeResourceId = (resourceId) => {
193 | this.setState({ resourceId });
194 | };
195 |
196 | render() {
197 | const { success } = this.state.ui;
198 |
199 | return (
200 |
251 | );
252 | }
253 | }
254 |
255 |
256 | ResourceEditor.defaultProps = {
257 | config: {
258 | authToken: "be270cae-1c77-4853-b8c1-30b6cf5e9878",
259 | api: "http://127.0.0.1:5000",
260 | lfs: "http://localhost:9419",
261 | organizationId: "myorg",
262 | datasetId: "sample_1",
263 | },
264 | };
265 |
266 | ResourceEditor.propTypes = {
267 | config: PropTypes.object.isRequired,
268 | };
269 |
270 | export default ResourceEditor;
--------------------------------------------------------------------------------
/examples/table-schema/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/examples/table-schema/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/table-schema/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 |
6 | const element = document.getElementById('ResourceEditor');
7 | if (element) {
8 | const config = {
9 | datasetId: element.getAttribute('data-dataset-id'),
10 | api: element.getAttribute('data-api'),
11 | lfs: element.getAttribute('data-lfs'),
12 | authToken: element.getAttribute('data-auth-token'),
13 | organizationId: element.getAttribute('data-organization-id'),
14 | resourceId: element.getAttribute('data-resource-id')
15 | }
16 |
17 | ReactDOM.render(
18 |
19 |
23 | ,
24 | element
25 | );
26 | }
27 |
28 |
29 | export { ResourceEditor } from './App';
--------------------------------------------------------------------------------
/examples/table-schema/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "datapub",
3 | "version": "0.2.3",
4 | "private": false,
5 | "main": "dist/core.js",
6 | "module": "dist/core.js",
7 | "files": [
8 | "dist",
9 | "README.md"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/datopian/datapub"
14 | },
15 | "dependencies": {
16 | "axios": "^0.19.2",
17 | "ckan-client": "git+https://github.com/datopian/ckan-client-js.git#v0.4.3",
18 | "frictionless-ckan-mapper-js": "^1.0.0",
19 | "frictionless.js": "^0.13.4",
20 | "react-scripts": "^3.4.3",
21 | "react-table": "^7.5.0",
22 | "stream-to-array": "^2.3.0",
23 | "uuidv4": "^6.2.3"
24 | },
25 | "peerDependencies": {
26 | "react": "^16.13.0",
27 | "react-dom": "^16.13.0"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build:ckan": "react-scripts build",
32 | "build": "rm -rf dist && NODE_ENV=production babel src --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__",
33 | "dist": "rm -rf dist/ && mkdir dist && mkdir dist/css && NODE_ENV=production babel src/ -d dist/ --copy-files",
34 | "test": "react-scripts test",
35 | "test:watch": "npm run test -- --coverage --watchAll",
36 | "eject": "react-scripts eject",
37 | "storybook": "start-storybook -p 6006",
38 | "build-storybook": "build-storybook",
39 | "deploy-storybook": "storybook-to-ghpages"
40 | },
41 | "eslintConfig": {
42 | "extends": "react-app"
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | },
56 | "devDependencies": {
57 | "@babel/cli": "^7.11.6",
58 | "@babel/core": "^7.11.1",
59 | "@babel/preset-env": "^7.11.5",
60 | "@babel/preset-react": "^7.10.4",
61 | "@storybook/addon-actions": "^6.0.16",
62 | "@storybook/addon-controls": "^6.0.16",
63 | "@storybook/addon-docs": "^6.0.21",
64 | "@storybook/addon-info": "^5.3.21",
65 | "@storybook/addon-links": "^6.0.16",
66 | "@storybook/addons": "^6.0.16",
67 | "@storybook/react": "^6.0.16",
68 | "@storybook/storybook-deployer": "^2.8.6",
69 | "@testing-library/jest-dom": "^4.2.4",
70 | "@testing-library/react": "^9.3.2",
71 | "@testing-library/user-event": "^7.1.2",
72 | "babel-loader": "^8.1.0",
73 | "enzyme": "^3.11.0",
74 | "enzyme-adapter-react-16": "^1.15.3",
75 | "eslint-plugin-react-hooks": "^4.1.2",
76 | "react-test-renderer": "^16.13.1"
77 | },
78 | "babel": {
79 | "presets": [
80 | "@babel/preset-env",
81 | "@babel/preset-react"
82 | ],
83 | "plugins": [
84 | "@babel/plugin-proposal-class-properties"
85 | ]
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datopian/datapub/d0662ab261e849ae47a45043a53eaa0689bd737a/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | .App {
16 | text-align: center;
17 | min-height: 100vh;
18 | color: #222;
19 | }
20 |
21 | .upload-wrapper {
22 | width: 100%;
23 | min-width: 360px;
24 | max-width: 960px;
25 | margin: 0 auto;
26 | background-color: #fff;
27 | padding: 32px 24px;
28 | border-radius: 12px;
29 | min-height: 60%;
30 | display: grid;
31 | grid-template-columns: 1fr 1fr;
32 | grid-template-rows: auto 300px auto auto;
33 | grid-template-areas:
34 | "header header"
35 | "uploadArea uploadArea"
36 | "editArea editArea";
37 | row-gap: 20px;
38 | column-gap: 20px;
39 | }
40 |
41 | .upload-header {
42 | grid-area: header;
43 | border-bottom: 1px solid #e3ebff;
44 | }
45 |
46 | .upload-header__title {
47 | color: #3f4656;
48 | font-weight: 400;
49 | }
50 |
51 | .upload-area {
52 | grid-area: uploadArea;
53 | display: grid;
54 | grid-template-columns: 40% auto;
55 | grid-template-rows: 1fr 1fr;
56 | grid-template-areas: "drop info";
57 | align-items: center;
58 | }
59 |
60 | .upload-area__info {
61 | grid-area: info;
62 | padding-top: 20px;
63 | }
64 |
65 | .upload-list {
66 | padding: 0;
67 | }
68 |
69 | .list-item {
70 | display: flex;
71 | }
72 |
73 | .upload-list-item {
74 | margin: 0 auto;
75 | background-color: #fff;
76 | padding: 8px 24px;
77 | list-style: none;
78 | max-width: 480px;
79 | width: 85%;
80 | border-top-left-radius: 8px;
81 | border-bottom-left-radius: 8px;
82 | color: #222;
83 | display: flex;
84 | align-items: center;
85 | justify-content: space-between;
86 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
87 | }
88 |
89 | .upload-file-name {
90 | max-width: 19ch;
91 | white-space: nowrap;
92 | overflow: hidden;
93 | text-overflow: ellipsis;
94 | color: #3f4656;
95 | }
96 |
97 | .upload-file-size {
98 | text-align: left;
99 | font-size: 0.75rem;
100 | color: lightslategrey;
101 | }
102 |
103 | .upload-message {
104 | color: #3f4656;
105 | }
106 |
107 | .upload-switcher {
108 | grid-area: switcher;
109 | }
110 |
111 | .upload-edit-area {
112 | grid-area: editArea;
113 | }
114 |
115 | .btn {
116 | max-width: 220px;
117 | margin: 2rem;
118 | border-radius: 5px;
119 | padding: 10px 25px;
120 | height: 45px;
121 | font-size: 1.2rem;
122 | color: #fff;
123 | background-color: #00A7E1;
124 | outline: none;
125 | border: none;
126 | cursor: pointer;
127 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
128 | }
129 |
130 | .btn:disabled {
131 | background: #e3e9ed !important;
132 | cursor: default;
133 | }
134 |
135 | .btn:hover {
136 | background-color: #009ace;
137 | }
138 |
139 | .btn-delete {
140 | margin: 2rem;
141 | border-radius: 5px;
142 | padding: 10px 25px;
143 | height: 45px;
144 | font-size: 1.2rem;
145 | color: #fff;
146 | outline: none;
147 | border: none;
148 | cursor: pointer;
149 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
150 | background-color: #F11301;
151 | }
152 |
153 | .btn-delete :disabled {
154 | background: #e3e9ed !important;
155 | cursor: default;
156 | }
157 |
158 | .btn-delete:hover {
159 | background-color: #C70E00;
160 | }
161 |
162 | .resource-edit-actions {
163 | display: flex;
164 | justify-content: space-between;
165 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Client } from "ckan-client";
3 | import PropTypes from "prop-types";
4 | import frictionlessCkanMapper from "frictionless-ckan-mapper-js";
5 | import { v4 as uuidv4 } from "uuid";
6 |
7 | import Metadata from "./components/Metadata";
8 | import TableSchema from "./components/TableSchema";
9 | import Upload from "./components/Upload";
10 | import "./App.css";
11 | import { removeHyphen } from "./utils";
12 |
13 | export class ResourceEditor extends React.Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | datasetId: this.props.config.datasetId,
18 | resourceId: "",
19 | resource: this.props.resource || {},
20 | ui: {
21 | fileOrLink: "",
22 | uploadComplete: undefined,
23 | success: false,
24 | error: false,
25 | loading: false,
26 | },
27 | client: null,
28 | isResourceEdit: false,
29 | };
30 | this.metadataHandler = this.metadataHandler.bind(this);
31 | }
32 |
33 | async componentDidMount() {
34 | const { config } = this.props;
35 | const {
36 | authToken,
37 | api,
38 | lfs,
39 | organizationId,
40 | datasetId,
41 | resourceId,
42 | } = config;
43 |
44 | const client = new Client(
45 | `${authToken}`,
46 | `${organizationId}`,
47 | `${datasetId}`,
48 | `${api}`,
49 | `${lfs}`
50 | );
51 |
52 | //Check if the user is editing resource
53 | if (resourceId) {
54 | const resource = await client.action("resource_show", { id: resourceId });
55 | const resourceSchema = await client.action("resource_schema_show", {
56 | id: resourceId,
57 | });
58 | const resourceSample = await client.action("resource_sample_show", {
59 | id: resourceId,
60 | });
61 |
62 | let resourceCopy = resource.result;
63 | let sampleCopy = [];
64 |
65 | try {
66 | // push the values to an array
67 | for (const property in resourceSample.result) {
68 | sampleCopy.push(resourceSample.result[property]);
69 | }
70 | // push sample as an array to be able to render in tableschema component
71 | resourceCopy.sample = sampleCopy;
72 | resourceCopy.schema = resourceSchema.result;
73 | } catch (e) {
74 | console.error(e);
75 | //generate empty values not to break the tableschema component
76 | resourceCopy.schema = { fields: [] };
77 | resourceCopy.sample = [];
78 | }
79 |
80 | return this.setState({
81 | client,
82 | resourceId,
83 | resource: resourceCopy,
84 | isResourceEdit: true,
85 | });
86 | }
87 |
88 | this.setState({ client });
89 | }
90 |
91 | metadataHandler(resource) {
92 | this.setState({
93 | resource,
94 | });
95 | }
96 |
97 | handleChangeMetadata = (event) => {
98 | const target = event.target;
99 | const value = target.value;
100 | const name = target.name;
101 | let resourceCopy = this.state.resource;
102 | resourceCopy[name] = value;
103 |
104 | this.setState({
105 | resource: resourceCopy,
106 | });
107 | };
108 |
109 | handleSubmitMetadata = async () => {
110 | const { resource, client } = this.state;
111 |
112 | await this.createResource(resource);
113 |
114 | // Change state of dataset to active if draft atm
115 | // this relates to how CKAN v2 has a phased dataset creation. See e.g.
116 | // https://github.com/ckan/ckan/blob/master/ckan/controllers/package.py#L917
117 |
118 | // only need to do this test if in resource create mode if editing a
119 | // resource this is unnecessary
120 | // TODO: update this in future to check for edit mode
121 | const isResourceCreate = true;
122 | if (isResourceCreate) {
123 | const datasetMetadata = await client.action("package_show", {
124 | id: this.state.datasetId,
125 | });
126 | let result = datasetMetadata.result;
127 |
128 | if (result.state == "draft") {
129 | result.state = "active";
130 | await client.action("package_update", result);
131 | }
132 | }
133 |
134 | // Redirect to dataset page
135 | return (window.location.href = `/dataset/${this.state.datasetId}`);
136 | };
137 |
138 | createResource = async (resource) => {
139 | const { client } = this.state;
140 | const { config } = this.props;
141 | const { organizationId, datasetId, resourceId } = config;
142 |
143 | const ckanResource = frictionlessCkanMapper.resourceFrictionlessToCkan(
144 | resource
145 | );
146 |
147 | //create a valid format from sample
148 | let data = { ...ckanResource.sample };
149 | //delete sample because is an invalid format
150 | delete ckanResource.sample;
151 | //generate an unique id for bq_table_name property
152 | let bqTableName = ckanResource.bq_table_name
153 | ? ckanResource.bq_table_name
154 | : uuidv4();
155 | // create a copy from ckanResource to add package_id, name, url, sha256,size, lfs_prefix, url, url_type
156 | // without this properties ckan-blob-storage doesn't work properly
157 | let ckanResourceCopy = {
158 | ...ckanResource,
159 | package_id: this.state.datasetId,
160 | name: resource.name || resource.title,
161 | sha256: resource.hash,
162 | size: resource.size,
163 | lfs_prefix: `${organizationId}/${datasetId}`,
164 | url: resource.name,
165 | url_type: "upload",
166 | bq_table_name: removeHyphen(bqTableName),
167 | sample: data,
168 | };
169 |
170 | //Check if the user is editing resource, call resource_update and redirect to the dataset page
171 | if (resourceId) {
172 | ckanResourceCopy = {
173 | ...ckanResourceCopy,
174 | id: resourceId,
175 | };
176 | await client.action("resource_update", ckanResourceCopy);
177 |
178 | return (window.location.href = `/dataset/${datasetId}`);
179 | }
180 | await client
181 | .action("resource_create", ckanResourceCopy)
182 | .then((response) => {
183 | this.onChangeResourceId(response.result.id);
184 | });
185 | };
186 |
187 | deleteResource = async () => {
188 | const { resource, client, datasetId } = this.state;
189 | if (window.confirm("Are you sure to delete this resource?")) {
190 | await client.action("resource_delete", { id: resource.id });
191 |
192 | return (window.location.href = `/dataset/${datasetId}`);
193 | }
194 | };
195 |
196 | handleUploadStatus = (status) => {
197 | const { ui } = this.state;
198 | const newUiState = {
199 | ...ui,
200 | success: status.success,
201 | error: status.error,
202 | loading: status.loading,
203 | };
204 |
205 | this.setState({ ui: newUiState });
206 | };
207 |
208 | onChangeResourceId = (resourceId) => {
209 | this.setState({ resourceId });
210 | };
211 |
212 | render() {
213 | const { loading, success } = this.state.ui;
214 |
215 | return (
216 |
270 | );
271 | }
272 | }
273 |
274 | /**
275 | * If the parent component doesn't specify a `config` and scope prop, then
276 | * the default values will be used.
277 | * */
278 | ResourceEditor.defaultProps = {
279 | config: {
280 | authToken: "be270cae-1c77-4853-b8c1-30b6cf5e9878",
281 | api: "http://localhost:5000",
282 | lfs: "http://localhost:5001", // Feel free to modify this
283 | organizationId: "myorg",
284 | datasetId: "data-test-2",
285 | },
286 | };
287 |
288 | ResourceEditor.propTypes = {
289 | config: PropTypes.object.isRequired,
290 | };
291 |
292 | export default ResourceEditor;
293 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import App from "./App";
5 |
6 | describe(" ", () => {
7 |
8 | it("should render in add a resource", () => {
9 | const wrapper = shallow( );
10 |
11 | expect(wrapper.find(".btn")).toHaveLength(1)
12 | expect(wrapper.find(".btn-delete")).toHaveLength(0)
13 | });
14 | })
--------------------------------------------------------------------------------
/src/assets/images/computing-cloud.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/Choose/Choose.css:
--------------------------------------------------------------------------------
1 | .upload-choose {
2 |
3 | }
4 |
5 | .choose-btn {
6 | padding: 10px;
7 | width: 90%;
8 | background-color: #4FA8FD;
9 | border: none;
10 | border-radius: 3px;
11 | color: #fff;
12 | font-weight: 500;
13 | cursor: pointer;
14 | outline: 0;
15 | font-weight: bold;
16 | }
17 | .choose-text {
18 | font-size: 1rem;
19 | font-weight: 300;
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Choose/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import PropTypes from "prop-types";
3 | import InputFile from "../InputFile";
4 | import InputUrl from "../InputUrl";
5 | import './Choose.css'
6 |
7 | const Choose = ({ onChangeUrl, onChangeHandler }) => {
8 | const [uploadOption, setUploadOption] = useState(false);
9 |
10 | return (
11 |
12 | {uploadOption ? (
13 | <>
14 | {uploadOption === "file" && (
15 |
16 | )}
17 | {uploadOption === "url" &&
}
18 | >
19 | ) : (
20 |
21 |
setUploadOption("file")}>Choose a file to Upload
22 |
OR
23 |
setUploadOption("url")}>Link a file already online
24 |
25 | )}
26 |
27 | );
28 | };
29 |
30 | Choose.propTypes = {
31 | onChangeUrl: PropTypes.func.isRequired,
32 | onChangeHandler: PropTypes.func.isRequired,
33 | };
34 |
35 | export default Choose;
36 |
--------------------------------------------------------------------------------
/src/components/InputFile/InputFile.css:
--------------------------------------------------------------------------------
1 |
2 | .upload-area__drop {
3 | position: relative;
4 | text-align: center;
5 | display: flex;
6 | justify-content: center;
7 | margin-top: 20px;
8 | height: 220px;
9 | }
10 |
11 | .upload-area__drop__input {
12 | grid-area: drop;
13 | padding: 1rem 1rem;
14 | position: relative;
15 | display: grid;
16 | grid-template-columns: auto auto;
17 | align-items: center;
18 | min-height: 200px;
19 | border: 1px dashed #3f4c6b;
20 | margin: 0px auto;
21 | width: 95%;
22 | border-radius: 8px;
23 | cursor: pointer;
24 | outline: 0;
25 | box-sizing: border-box;
26 | z-index: 10;
27 | color: transparent;
28 | }
29 |
30 | .upload-area__drop__input::-webkit-file-upload-button {
31 | flex-shrink: 0;
32 | font-size: 0.65rem;
33 | color: #ffffff;
34 | border-radius: 3px;
35 | padding: 8px 15px;
36 | margin: 0;
37 | text-transform: uppercase;
38 | text-align: center;
39 | border: none;
40 | outline: 0;
41 | margin: 0 auto;
42 | visibility: hidden;
43 | }
44 |
45 | .upload-area__drop__icon {
46 | width: 80px;
47 | position: absolute;
48 | top: 20%;
49 | left: 40%;
50 | cursor: pointer;
51 | fill: #e3ebff;
52 | }
53 |
54 | .upload-area__drop__text {
55 | color: lightslategrey;
56 | opacity: 0.7;
57 | font-weight: 400;
58 | position: absolute;
59 | top: 55%;
60 | font-size: 0.78rem;
61 | cursor: pointer;
62 | }
--------------------------------------------------------------------------------
/src/components/InputFile/InputFile.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import InputFile from ".";
5 |
6 | describe(" ", () => {
7 | const onChangeHandler = jest.fn(() => "sample");
8 |
9 | it("render InputFile without crashing", () => {
10 | const wrapper = shallow( );
11 | expect(wrapper.contains("Drag and drop your files")).toEqual(true);
12 | });
13 |
14 | it("can select a file", () => {
15 | const wrapper = shallow( );
16 | const input = wrapper.find("input");
17 |
18 | input.simulate("change", {
19 | target: {
20 | files: ["sample.csv"],
21 | },
22 | });
23 |
24 | expect(onChangeHandler).toHaveBeenCalled();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/components/InputFile/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import './InputFile.css'
4 | import UploadIcon from "../../assets/images/computing-cloud.svg";
5 |
6 | const InputFile = ({ onChangeHandler }) => {
7 |
8 | return (
9 |
10 |
16 |
21 |
22 | Drag and drop your files
23 |
24 | or
25 | click to select
26 |
27 |
28 | )
29 | }
30 |
31 | InputFile.propTypes = {
32 | onChangeHandler: PropTypes.func.isRequired
33 | };
34 |
35 | export default InputFile;
36 |
--------------------------------------------------------------------------------
/src/components/InputUrl/InputUrl.css:
--------------------------------------------------------------------------------
1 | .upload-area__url {
2 | text-align: left;
3 | width: 95%;
4 | margin: 0 auto;
5 | }
6 |
7 | .upload-area__url__label {
8 | display: block;
9 | padding: 4px;
10 | }
11 |
12 | .upload-area__url__input {
13 | width: 100%;
14 | height: 40px;
15 | border-radius: 5px;
16 | box-shadow: none;
17 | border: 1px solid #ced6e0;
18 | transition: all 0.3s ease-in-out;
19 | font-size: 18px;
20 | padding: 5px 15px;
21 | background: none;
22 | color: #1a3b5d;
23 | font-family: "Source Sans Pro", sans-serif;
24 | box-sizing: border-box;
25 | font-size: 1rem;
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/InputUrl/InputUrl.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import InputUrl from ".";
5 |
6 | describe(" ", () => {
7 | const onChangeUrl = jest.fn(() => "https://www.datopian.com");
8 |
9 | it("render InputUrl without crashing", () => {
10 | const wrapper = shallow( );
11 | expect(wrapper.contains("URL:")).toEqual(true);
12 | });
13 |
14 | it("can edit url", () => {
15 | const wrapper = shallow( );
16 | const input = wrapper.find("input");
17 |
18 | input.simulate("blur", {
19 | target: {
20 | value: "https://www.datopian.com",
21 | },
22 | });
23 |
24 | expect(onChangeUrl).toHaveBeenCalled();
25 | });
26 | });
--------------------------------------------------------------------------------
/src/components/InputUrl/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import "./InputUrl.css";
4 |
5 | const InputUrl = ({ onChangeUrl }) => {
6 | const handleKeyPress = (event) => {
7 | if (event.keyCode === 13) {
8 | event.target.blur();
9 | }
10 | };
11 |
12 | return (
13 |
14 |
15 | URL:
16 |
17 | handleKeyPress(event)}
24 | placeholder="https://www.data.com/sample.csv"
25 | />
26 |
27 | );
28 | };
29 |
30 | InputUrl.propTypes = {
31 | onChangeUrl: PropTypes.func.isRequired,
32 | };
33 |
34 | export default InputUrl;
35 |
--------------------------------------------------------------------------------
/src/components/Metadata/Metadata.css:
--------------------------------------------------------------------------------
1 | .metadata-form {
2 | display: grid;
3 | grid-template-columns: 1fr 1fr;
4 | column-gap: 20px;
5 | }
6 |
7 | .metadata-name {
8 | color: #1a3b5d;
9 | text-align: left;
10 | grid-area: name;
11 | }
12 |
13 | .metadata-label {
14 | font-size: 14px;
15 | margin-bottom: 5px;
16 | margin-left: 5px;
17 | text-align: left;
18 | font-weight: 500;
19 | color: #1a3b5d;
20 | width: 100%;
21 | display: block;
22 | user-select: none;
23 | }
24 |
25 | .metadata-input {
26 | margin-bottom: 20px;
27 | }
28 |
29 | .metadata-input__input {
30 | width: 100%;
31 | height: 40px;
32 | border-radius: 5px;
33 | box-shadow: none;
34 | border: 1px solid #ced6e0;
35 | transition: all 0.3s ease-in-out;
36 | font-size: 18px;
37 | padding: 5px 15px;
38 | background: none;
39 | color: #1a3b5d;
40 | font-family: "Source Sans Pro", sans-serif;
41 | box-sizing: border-box;
42 | }
43 |
44 | .btn {
45 | margin-top: 17px;
46 | border-radius: 5px;
47 | padding: 10px;
48 | width: 50%;
49 | height: 45px;
50 | font-size: 1.2rem;
51 | color: #fff;
52 | background-color: #00A7E1;
53 | outline: none;
54 | border: none;
55 | cursor: pointer;
56 | box-shadow: 0 4px 11px 3px rgba(18, 22, 33, 0.05);
57 | }
58 |
59 | .btn:disabled {
60 | background: #e3e9ed !important;
61 | cursor: default;
62 | }
63 |
64 | .btn:hover {
65 | background-color: #009ace;
66 | }
--------------------------------------------------------------------------------
/src/components/Metadata/Metadata.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { shallow } from "enzyme";
4 |
5 | import Metadata from ".";
6 |
7 | describe(" ", () => {
8 | const handleChange = jest.fn(2);
9 | const handleSubmit = jest.fn();
10 | const deleteResource = jest.fn();
11 | const updateResource = jest.fn();
12 |
13 | it("render Metadata without crashing", () => {
14 | const div = document.createElement("div");
15 | ReactDOM.render(
16 | ,
25 | div
26 | );
27 | });
28 |
29 | it("auto-populate input fields", () => {
30 | const wrapper = shallow(
31 |
46 | );
47 | const inputTitle = wrapper.find("#title");
48 | const inputFormat = wrapper.find("#format");
49 | const inputDescription = wrapper.find("#description");
50 | const inputRestricted = wrapper.find("#restricted");
51 | const inputEncoding = wrapper.find("#encoding");
52 |
53 | expect(inputTitle.props().value).toEqual("sample");
54 | expect(inputFormat.props().value).toEqual("csv");
55 | expect(inputDescription.props().value).toEqual("Lorem ...");
56 | expect(inputRestricted.props().value).toEqual("private");
57 | expect(inputEncoding.props().value).toEqual("utf-8");
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/components/Metadata/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import "./Metadata.css";
5 | import encodeData from "../../db/encode.json";
6 | import formatData from "../../db/resource_formats.json";
7 |
8 | //TODO: add the custom fields as a props and render it in metadata component
9 | const customFields = [
10 | {
11 | label: "Access Restriction",
12 | name: "restricted",
13 | input_type: "select",
14 | values: ['{"level": "public"}', '{"level": "private"}'],
15 | options: ["Public", "Private"],
16 | },
17 | ];
18 |
19 | const Metadata = ({ metadata, handleChange}) => {
20 | return (
21 | <>
22 | {metadata.path}
23 |
24 |
25 |
26 | Title:
27 |
28 |
36 |
37 |
38 |
39 | Description:
40 |
41 |
49 |
50 |
51 |
52 | Encoding:
53 |
54 |
62 |
63 | Select...
64 |
65 | {encodeData.map((item) => (
66 |
67 | {item.label}
68 |
69 | ))}
70 |
71 |
72 |
73 |
74 | Format:
75 |
76 |
84 |
85 | Select...
86 |
87 | {formatData.map((item) => (
88 |
89 | {item[0]}
90 |
91 | ))}
92 |
93 |
94 | {customFields &&
95 | customFields.map((item) => (
96 |
97 |
98 | {item.label}:
99 |
100 |
108 |
109 | Select...
110 |
111 | {item.options.map((option, index) => (
112 |
116 | {option}
117 |
118 | ))}
119 |
120 |
121 | ))}
122 |
123 | >
124 | );
125 | };
126 |
127 | Metadata.propTypes = {
128 | metadata: PropTypes.object.isRequired,
129 | handleChange: PropTypes.func.isRequired,
130 | };
131 |
132 | export default Metadata;
133 |
--------------------------------------------------------------------------------
/src/components/ProgressBar/ProgressBar.css:
--------------------------------------------------------------------------------
1 | .svg{
2 | display: block;
3 | margin: 20px auto;
4 | max-width: 100%;
5 | }
6 |
7 | .svg-circle-bg {
8 | fill: none;
9 | }
10 |
11 | .svg-circle {
12 | fill: none;
13 | }
14 |
15 | .svg-circle-text {
16 | font-size: 0.68rem;
17 | text-anchor: middle;
18 | margin-top: 30px;
19 | fill:#3F4656;
20 | font-weight: bold;
21 | }
22 |
23 | .time-remaining {
24 | font-size: 0.55rem;
25 | float: left;
26 | margin-top: -13px;
27 | color: #3F4656;
28 | }
--------------------------------------------------------------------------------
/src/components/ProgressBar/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import "./ProgressBar.css";
5 |
6 | const ProgressBar = (props) => {
7 | const [offset, setOffset] = useState(0);
8 | const circleRef = useRef(null);
9 | const {
10 | size,
11 | progress,
12 | strokeWidth,
13 | circleOneStroke,
14 | circleTwoStroke,
15 | timeRemaining,
16 | } = props;
17 |
18 | const center = size / 2;
19 | const radius = size / 2 - strokeWidth / 2;
20 | const circumference = 2 * Math.PI * radius;
21 |
22 | useEffect(() => {
23 | const progressOffset = ((100 - progress) / 100) * circumference;
24 | setOffset(progressOffset);
25 |
26 | circleRef.current.style = "transition: stroke-dashoffset 850ms ease-in-out";
27 | }, [setOffset, progress, circumference, offset]);
28 |
29 | return (
30 | <>
31 |
32 |
40 |
51 |
52 | {progress}%
53 |
54 |
55 | {timeRemaining > 0 && {timeRemaining > 60 ? `${Math.floor(timeRemaining /60)} minute${Math.floor(timeRemaining /60) > 1 ? 's' : ''}` : `${Math.floor(timeRemaining)} second${timeRemaining > 1 ? 's' : ''}`} left }
56 | >
57 | );
58 | };
59 |
60 | ProgressBar.propTypes = {
61 | size: PropTypes.number.isRequired,
62 | progress: PropTypes.number.isRequired,
63 | strokeWidth: PropTypes.number.isRequired,
64 | circleOneStroke: PropTypes.string.isRequired,
65 | circleTwoStroke: PropTypes.string.isRequired,
66 | };
67 |
68 | export default ProgressBar;
69 |
--------------------------------------------------------------------------------
/src/components/Switcher/Switcher.css:
--------------------------------------------------------------------------------
1 | .switch-wrapper {
2 | color: #222222;
3 | display: grid;
4 | grid-template-columns: 160px 200px 160px;
5 | grid-template-areas: "switchOne divLine switchTwo";
6 | width: 80%;
7 | align-items: center;
8 | justify-items: center;
9 | align-content: center;
10 | justify-content: center;
11 | margin: 1rem auto;
12 | }
13 |
14 | .switch-number {
15 | border-radius: 50%;
16 | width: 20px;
17 | height: 20px;
18 | padding: 3px;
19 | text-align: center;
20 | margin-bottom: 1.2rem;
21 | margin: 0 auto;
22 | cursor: pointer;
23 | }
24 |
25 | .switch-one {
26 | grid-area: switchOne;
27 | }
28 |
29 | .switch-two {
30 | grid-area: switchTwo;
31 | }
32 |
33 | .switch-number-selected {
34 | background-color: #00A7E1;
35 | color: #ffffff;
36 | }
37 |
38 | .switch-number-disabled {
39 | border: 1px solid #e3ebff;
40 | }
41 |
42 | .switch-description {
43 | margin-top: 1rem;
44 | width: 100%;
45 | cursor: pointer;
46 | }
47 |
48 | .switch-description-active {
49 | font-weight: bold;
50 | }
51 |
52 | .divider-line {
53 | grid-area: divLine;
54 | height: 2px;
55 | background-color: #e3ebff;
56 | width: 100%;
57 | margin-bottom: 3rem;
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/Switcher/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import './Switcher.css'
4 |
5 | const Switcher = (props) => {
6 |
7 | return (
8 |
9 |
props.switcher('metadata')}>
10 |
11 | 1
12 |
13 |
14 | Edit Metadata
15 |
16 |
17 |
props.switcher('schema')}>
18 |
19 | 2
20 |
21 |
22 | Edit Schema
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | Switcher.propTypes = {
31 | metadataOrSchema: PropTypes.string.isRequired
32 | };
33 |
34 | export default Switcher;
35 |
--------------------------------------------------------------------------------
/src/components/TableSchema/TableSchema.css:
--------------------------------------------------------------------------------
1 | .table-container {
2 | margin-top: 2.6rem;
3 | margin-bottom: 2rem;
4 | display: flex;
5 | }
6 |
7 | .table-schema-info_container {
8 | overflow: overlay;
9 | }
10 |
11 | .table-schema-info_container::-webkit-scrollbar-track {
12 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
13 | border-radius: 10px;
14 | background-color: #fff;
15 | }
16 |
17 | .table-schema-info_container::-webkit-scrollbar {
18 | width: 8px;
19 | height: 8px;
20 | background-color: #fff;
21 | }
22 |
23 | .table-schema-info_container::-webkit-scrollbar-thumb {
24 | border-radius: 10px;
25 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
26 | background-color: #e3e9ed;
27 | }
28 |
29 | .table-schema-info_table {
30 | width: auto !important;
31 | }
32 |
33 | .table-schema-help {
34 | vertical-align: middle;
35 | }
36 |
37 | .table-schema-help_row {
38 | padding: 1.3rem;
39 | }
40 |
41 | .table-thead-tr {
42 | height: 50px;
43 | border-radius: 8px;
44 | }
45 |
46 | .table-thead-th {
47 | background: #f1f1f1;
48 | color: #363844;
49 | font-weight: bold;
50 | text-align: center;
51 | vertical-align: middle;
52 | position: sticky;
53 | top: 0;
54 | z-index: 10;
55 | }
56 |
57 | .table-tbody-help-tr {
58 | color: #363844;
59 | height: 40px;
60 | }
61 |
62 | .table-tbody-help-td {
63 | vertical-align: middle;
64 | font-weight: bold;
65 | }
66 |
67 | .table-tbody-help-td-empty {
68 | height: 50px;
69 | }
70 |
71 | .table-tbody-td {
72 | padding: 5px;
73 | border: solid 1px #f1f1f1;
74 | background: #fff;
75 | max-width: 100px;
76 | overflow: hidden;
77 | text-overflow: ellipsis;
78 | white-space: nowrap;
79 | font-size: 14px;
80 | }
81 |
82 | .table-tbody-input {
83 | border: none;
84 | padding: 0 10px 0 10px;
85 | height: 40px;
86 | }
87 |
88 | .table-tbody-select {
89 | position: relative;
90 | display: inline-block;
91 | vertical-align: middle;
92 | background-color: #fafafa;
93 | width: 100%;
94 | border: none;
95 | padding: 10px;
96 | height: 40px;
97 | }
98 |
99 | .table-btn-save {
100 | padding: 10px;
101 | width: 90%;
102 | background-color: #4fa8fd;
103 | border: none;
104 | border-radius: 3px;
105 | color: #fff;
106 | font-weight: 500;
107 | cursor: pointer;
108 | outline: 0;
109 | }
110 |
111 | .table-btn-save:disabled {
112 | background: #e3e9ed !important;
113 | cursor: default;
114 | }
115 |
--------------------------------------------------------------------------------
/src/components/TableSchema/TableSchema.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 |
4 | import TableSchema from ".";
5 |
6 | describe(" ", () => {
7 | const onChange = jest.fn(() => "https://www.datopian.com");
8 |
9 | it("render TableSchema without crashing", () => {
10 | const wrapper = shallow( );
11 |
12 |
13 | expect(wrapper.contains("Title")).toEqual(true);
14 | expect(wrapper.contains("Description")).toEqual(true);
15 | expect(wrapper.contains("Type")).toEqual(true);
16 | expect(wrapper.contains("Format")).toEqual(true);
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/src/components/TableSchema/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import { useTable } from "react-table";
4 | import types from "../../db/types.json";
5 |
6 | import "./TableSchema.css";
7 |
8 | const TableSchema = (props) => {
9 | const [schema, setSchema] = useState(props.schema);
10 | // eslint-disable-next-line react-hooks/exhaustive-deps
11 | const data = React.useMemo(() => [...props.data], [schema]);
12 |
13 | const columnsSchema = schema.fields.map((item, index) => {
14 | return {
15 | Header: item.name ? item.name : `column_${index + 1}`,
16 | accessor: item.name ? item.name : `column_${index + 1}`,
17 | };
18 | });
19 | // eslint-disable-next-line react-hooks/exhaustive-deps
20 | const columns = React.useMemo(() => [...columnsSchema], [schema]);
21 | const {
22 | getTableProps,
23 | getTableBodyProps,
24 | headerGroups,
25 | rows,
26 | prepareRow,
27 | } = useTable({ columns, data });
28 |
29 | const handleChange = (event, key, index) => {
30 | const newSchema = { ...schema };
31 | newSchema.fields[index][key] = event.target.value;
32 | setSchema(newSchema);
33 | };
34 |
35 | //if the the user upload a new file, will update the state
36 | //and render with the new values
37 | useEffect(() => {
38 | setSchema(props.schema);
39 | }, [props.schema]);
40 |
41 | const renderEditSchemaField = (key) => {
42 | if (key === "type") {
43 | return schema.fields.map((item, index) => (
44 |
45 | handleChange(event, key, index)}
49 | >
50 | {types.type.map((item, index) => {
51 | return (
52 |
56 | {item}
57 |
58 | );
59 | })}
60 |
61 |
62 | ));
63 | }
64 | return schema.fields.map((item, index) => (
65 |
66 | handleChange(event, key, index)}
71 | />
72 |
73 | ));
74 | };
75 |
76 | return (
77 | <>
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Title
86 |
87 |
88 | Description
89 |
90 |
91 | Type
92 |
93 |
94 | Format
95 |
96 |
97 |
98 |
99 |
100 |
101 | {headerGroups.map((headerGroup) => (
102 |
106 | {headerGroup.headers.map((column) => (
107 |
108 | {column.render("Header")}
109 |
110 | ))}
111 |
112 | ))}
113 |
114 |
115 |
116 | {renderEditSchemaField("title")}
117 |
118 |
119 | {renderEditSchemaField("description")}
120 |
121 | {renderEditSchemaField("type")}
122 | {renderEditSchemaField("format")}
123 | {rows.map((row) => {
124 | prepareRow(row);
125 | return (
126 |
127 | {row.cells.map((cell) => {
128 | return (
129 |
130 | {cell.render("Cell")}
131 |
132 | );
133 | })}
134 |
135 | );
136 | })}
137 |
138 |
139 |
140 |
141 | >
142 | );
143 | };
144 |
145 | TableSchema.propTypes = {
146 | schema: PropTypes.object.isRequired,
147 | data: PropTypes.array.isRequired,
148 | };
149 |
150 | export default TableSchema;
151 |
--------------------------------------------------------------------------------
/src/components/Upload/Upload.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, fireEvent } from "@testing-library/react";
3 | import ReactDOM from "react-dom";
4 | import { shallow } from "enzyme";
5 |
6 | import Upload from ".";
7 |
8 | describe(" ", () => {
9 | it("render Upload without crashing", () => {
10 | const div = document.createElement("div");
11 | ReactDOM.render( , div);
12 | });
13 |
14 | it.skip('can select a file', () => {
15 | const wrapper = shallow( )
16 | const input = wrapper.find('input');
17 |
18 | input.simulate('change', {
19 | target: {
20 | files: [
21 | 'sample.csv'
22 | ]
23 | }
24 | });
25 |
26 | expect(wrapper.state().selectedFile).toEqual('sample.csv');
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/Upload/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as data from "frictionless.js";
3 | import toArray from "stream-to-array";
4 | import ProgressBar from "../ProgressBar";
5 | import { onFormatBytes } from "../../utils";
6 | import Choose from "../Choose";
7 |
8 | class Upload extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | datasetId: props.datasetId,
13 | selectedFile: null,
14 | fileSize: 0,
15 | formattedSize: "0 KB",
16 | start: "",
17 | loaded: 0,
18 | success: false,
19 | error: false,
20 | fileExists: false,
21 | loading: false,
22 | timeRemaining: 0,
23 | };
24 | }
25 |
26 | onChangeHandler = async (event) => {
27 | let { formattedSize, selectedFile } = this.state;
28 |
29 | if (event.target.files.length > 0) {
30 | selectedFile = event.target.files[0];
31 | const file = data.open(selectedFile);
32 | try {
33 | await file.addSchema();
34 | } catch (e) {
35 | console.warn(e);
36 | }
37 | formattedSize = onFormatBytes(file.size);
38 | const hash = await file.hash('sha256');
39 | this.props.metadataHandler(Object.assign(file.descriptor, { hash }));
40 | }
41 |
42 | this.setState({
43 | selectedFile,
44 | loaded: 0,
45 | success: false,
46 | fileExists: false,
47 | error: false,
48 | formattedSize,
49 | });
50 |
51 | await this.onClickHandler();
52 | };
53 |
54 | onUploadProgress = (progressEvent) => {
55 | this.onTimeRemaining(progressEvent.loaded);
56 | this.setState({
57 | loaded: (progressEvent.loaded / progressEvent.total) * 100,
58 | });
59 | };
60 |
61 | onTimeRemaining = (progressLoaded) => {
62 | const end = new Date().getTime();
63 | const duration = (end - this.state.start) / 1000;
64 | const bps = progressLoaded / duration;
65 | const kbps = bps / 1024;
66 | const timeRemaining = (this.state.fileSize - progressLoaded) / kbps;
67 |
68 | this.setState({
69 | timeRemaining: timeRemaining / 1000,
70 | });
71 | };
72 |
73 | onClickHandler = async () => {
74 | const start = new Date().getTime();
75 | const { selectedFile } = this.state;
76 | const { client } = this.props;
77 |
78 | const resource = data.open(selectedFile);
79 |
80 | this.setState({
81 | fileSize: resource.size,
82 | start,
83 | loading: true,
84 | });
85 |
86 | this.props.handleUploadStatus({
87 | loading: true,
88 | error: false,
89 | success: false,
90 | });
91 |
92 | // Use client to upload file to the storage and track the progress
93 | client
94 | .pushBlob(resource, this.onUploadProgress)
95 | .then((response) => {
96 | this.setState({
97 | success: true,
98 | loading: false,
99 | fileExists: ! response,
100 | loaded: 100
101 | });
102 | this.props.handleUploadStatus({
103 | loading: false,
104 | success: true,
105 | });
106 | })
107 | .catch((error) => {
108 | console.error("Upload failed with error: " + error);
109 | this.setState({ error: true, loading: false });
110 | this.props.handleUploadStatus({
111 | loading: false,
112 | success: false,
113 | error: true,
114 | });
115 | });
116 | };
117 |
118 | render() {
119 | const {
120 | success,
121 | fileExists,
122 | error,
123 | timeRemaining,
124 | selectedFile,
125 | formattedSize,
126 | } = this.state;
127 | return (
128 |
129 |
console.log("Get url:", event.target.value)}
132 | />
133 |
134 | {selectedFile && (
135 | <>
136 |
137 |
138 |
139 |
140 |
{selectedFile.name}
141 |
{formattedSize}
142 |
143 |
153 |
154 |
155 |
156 |
157 | {success && !fileExists && !error && "File uploaded successfully"}
158 | {fileExists && "File uploaded successfully"}
159 | {error && "Upload failed"}
160 |
161 | >
162 | )}
163 |
164 |
165 | );
166 | }
167 | }
168 |
169 | export default Upload;
170 |
--------------------------------------------------------------------------------
/src/core.js:
--------------------------------------------------------------------------------
1 | import Choose from "./components/Choose";
2 | import InputFile from "./components/InputFile";
3 | import InputUrl from "./components/InputUrl";
4 | import Metadata from "./components/Metadata";
5 | import ProgressBar from "./components/ProgressBar";
6 | import Switcher from "./components/Switcher";
7 | import TableSchema from "./components/TableSchema";
8 | import Upload from "./components/Upload";
9 | import encodeData from "./db/encode.json";
10 | import formatData from "./db/resource_formats.json";
11 | import licenses from "./db/licenses.json";
12 | import types from "./db/types.json";
13 |
14 | export {
15 | Upload,
16 | Choose,
17 | InputFile,
18 | InputUrl,
19 | Metadata,
20 | ProgressBar,
21 | Switcher,
22 | TableSchema,
23 | encodeData,
24 | formatData,
25 | licenses,
26 | types,
27 | };
28 |
--------------------------------------------------------------------------------
/src/db/encode.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "value": "utf_8",
4 | "label": "UTF-8"
5 | },
6 | {
7 | "value": "iso_8859_1",
8 | "label": "ISO-8859-1"
9 | },
10 | {
11 | "value": "windows_1251",
12 | "label": "Windows-1251"
13 | },
14 | {
15 | "value": "windows_1252",
16 | "label": "Windows-1252"
17 | },
18 | {
19 | "value": "shift_jis",
20 | "label": "Shift JIS"
21 | },
22 | {
23 | "value": "gb2312",
24 | "label": "GB2312"
25 | },
26 | {
27 | "value": "euc_kr",
28 | "label": "EUC-KR"
29 | },
30 | {
31 | "value": "iso_8859_2",
32 | "label": "ISO-8859-2"
33 | },
34 | {
35 | "value": "windows_1250",
36 | "label": "Windows-1250"
37 | },
38 | {
39 | "value": "euc_jp",
40 | "label": "EUC-JP"
41 | },
42 | {
43 | "value": "gbk",
44 | "label": "GBK"
45 | },
46 | {
47 | "value": "big5",
48 | "label": "Big5"
49 | },
50 | {
51 | "value": "iso_8859_15",
52 | "label": "ISO-8859-15"
53 | },
54 | {
55 | "value": "windows_1256",
56 | "label": "Windows-1256"
57 | },
58 | {
59 | "value": "iso_8859_9",
60 | "label": "ISO-8859-9"
61 | }
62 | ]
--------------------------------------------------------------------------------
/src/db/licenses.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "value": "cc-by",
4 | "label": "Creative Commons Attribution"
5 | },
6 | {
7 | "value": "cc-by-sa",
8 | "label": "Creative Commons Attribution Share-Alike"
9 | },
10 | {
11 | "value": "cc-zero",
12 | "label": "Creative Commons CCZero"
13 | },
14 | {
15 | "value": "cc-nc",
16 | "label": "Creative Commons Non-Commercial (Any)"
17 | },
18 | {
19 | "value": "gfdl",
20 | "label": "GNU Free Documentation License"
21 | },
22 | {
23 | "value": "notspecified",
24 | "label": "License not specified"
25 | },
26 | {
27 | "value": "odc-by",
28 | "label": "Open Data Commons Attribution License"
29 | },
30 | {
31 | "value": "odc-odbl",
32 | "label": "Open Data Commons Open Database License (ODbL)"
33 | },
34 | {
35 | "value": "odc-pddl",
36 | "label": "Open Data Commons Public Domain Dedication and License (PDDL)"
37 | },
38 | {
39 | "value": "other-at",
40 | "label": "Other (Attribution)"
41 | },
42 | {
43 | "value": "other-nc",
44 | "label": "Other (Non-Commercial)"
45 | },
46 | {
47 | "value": "other-closed",
48 | "label": "Other (Not Open)"
49 | },
50 | {
51 | "value": "other-open",
52 | "label": "Other (Open)"
53 | },
54 | {
55 | "value": "other-pd",
56 | "label": "Other (Public Domain)"
57 | },
58 | {
59 | "value": "uk-ogl",
60 | "label": "UK Open Government Licence (OGL)"
61 | }
62 | ]
--------------------------------------------------------------------------------
/src/db/resource_formats.json:
--------------------------------------------------------------------------------
1 | [
2 | ["PPTX", "Powerpoint OOXML Presentation", "application/vnd.openxmlformats-officedocument.presentationml.presentation", []],
3 | ["EXE", "Windows Executable Program", "application/x-msdownload", []],
4 | ["DOC", "Word Document", "application/msword", []],
5 | ["KML", "KML File", "application/vnd.google-earth.kml+xml", []],
6 | ["XLS", "Excel Document", "application/vnd.ms-excel", ["Excel", "application/msexcel", "application/x-msexcel", "application/x-ms-excel", "application/x-excel", "application/x-dos_ms_excel", "application/xls", "application/x-xls"]],
7 | ["WCS", "Web Coverage Service", "wcs", []],
8 | ["JS", "JavaScript", "application/x-javascript", []],
9 | ["MDB", "Access Database", "application/x-msaccess", []],
10 | ["NetCDF", "NetCDF File", "application/netcdf", []],
11 | ["ArcGIS Map Service", "ArcGIS Map Service", "ArcGIS Map Service", ["arcgis map service"]],
12 | ["TSV", "Tab Separated Values File", "text/tab-separated-values", ["text/tsv"]],
13 | ["WFS", "Web Feature Service", null, []],
14 | ["WMTS", "Web Map Tile Service", null, []],
15 | ["ArcGIS Online Map", "ArcGIS Online Map", "ArcGIS Online Map", ["web map application"]],
16 | ["Perl", "Perl Script", "text/x-perl", []],
17 | ["KMZ", "KMZ File", "application/vnd.google-earth.kmz+xml", ["application/vnd.google-earth.kmz"]],
18 | ["OWL", "Web Ontology Language", "application/owl+xml", []],
19 | ["N3", "N3 Triples", "application/x-n3", []],
20 | ["ZIP", "Zip File", "application/zip", ["zip", "http://purl.org/NET/mediatypes/application/zip"]],
21 | ["GZ", "Gzip File", "application/gzip", ["application/x-gzip"]],
22 | ["QGIS", "QGIS File", "application/x-qgis", []],
23 | ["ODS", "OpenDocument Spreadsheet", "application/vnd.oasis.opendocument.spreadsheet", []],
24 | ["ODT", "OpenDocument Text", "application/vnd.oasis.opendocument.text", []],
25 | ["JSON", "JavaScript Object Notation", "application/json", []],
26 | ["BMP", "Bitmap Image File", "image/x-ms-bmp", []],
27 | ["HTML", "Web Page", "text/html", ["htm", "http://purl.org/net/mediatypes/text/html"]],
28 | ["RAR", "RAR Compressed File", "application/rar", []],
29 | ["TIFF", "TIFF Image File", "image/tiff", []],
30 | ["ODB", "OpenDocument Database", "application/vnd.oasis.opendocument.database", []],
31 | ["TXT", "Text File", "text/plain", []],
32 | ["DCR", "Adobe Shockwave format", "application/x-director", []],
33 | ["ODF", "OpenDocument Math Formula", "application/vnd.oasis.opendocument.formula", []],
34 | ["ODG", "OpenDocument Image", "application/vnd.oasis.opendocument.graphics", []],
35 | ["XML", "XML File", "application/xml", ["text/xml", "http://purl.org/net/mediatypes/application/xml"]],
36 | ["XLSX", "Excel OOXML Spreadsheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", []],
37 | ["DOCX", "Word OOXML Document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []],
38 | ["BIN", "Binary Data", "application/octet-stream", ["bin"]],
39 | ["XSLT", "Extensible Stylesheet Language Transformations", "application/xslt+xml", []],
40 | ["WMS", "Web Mapping Service", "WMS", ["wms"]],
41 | ["SVG", "SVG vector image", "image/svg+xml", ["svg"]],
42 | ["PPT", "Powerpoint Presentation", "application/vnd.ms-powerpoint", []],
43 | ["ODP", "OpenDocument Presentation", "application/vnd.oasis.opendocument.presentation", []],
44 | ["JPEG", "JPG Image File", "image/jpeg", ["jpeg", "jpg"]],
45 | ["SPARQL", "SPARQL end-point", "application/sparql-results+xml", []],
46 | ["GIF", "GIF Image File", "image/gif", []],
47 | ["RDF", "RDF", "application/rdf+xml", ["rdf/xml"]],
48 | ["E00", " ARC/INFO interchange file format", "application/x-e00", []],
49 | ["PDF", "PDF File", "application/pdf", []],
50 | ["CSV", "Comma Separated Values File", "text/csv", ["text/comma-separated-values"]],
51 | ["ODC", "OpenDocument Chart", "application/vnd.oasis.opendocument.chart", []],
52 | ["Atom Feed", "Atom Feed", "application/atom+xml", []],
53 | ["MrSID", "MrSID", "image/x-mrsid", []],
54 | ["ArcGIS Map Preview", "ArcGIS Map Preview", "ArcGIS Map Preview", ["arcgis map preview"]],
55 | ["XYZ", "XYZ Chemical File", "chemical/x-xyz", []],
56 | ["MOP", "MOPAC Input format", "chemical/x-mopac-input", []],
57 | ["Esri REST", "Esri Rest API Endpoint", "Esri REST", ["arcgis_rest"]],
58 | ["dBase", "dBase Database", "application/x-dbf", ["dbf"]],
59 | ["MXD", "ESRI ArcGIS project file", "application/x-mxd", []],
60 | ["TAR", "TAR Compressed File", "application/x-tar", []],
61 | ["PNG", "PNG Image File", "image/png", []],
62 | ["RSS", "RSS feed", "application/rss+xml", []],
63 | ["GeoJSON", "Geographic JavaScript Object Notation", "application/geo+json", ["geojson"]],
64 | ["SHP", "Shapefile", null, ["esri shapefile"]],
65 | ["TORRENT", "Torrent", "application/x-bittorrent", ["bittorrent"]],
66 | ["ICS", "iCalendar", "text/calendar", ["ifb", "iCal"]],
67 | ["GTFS", "General Transit Feed Specification", null, []],
68 | ["XLSB", "Excel OOXML Spreadsheet - Binary Workbook", "application/vnd.ms-excel.sheet.binary.macroEnabled.12", []],
69 | ["XLSM", "Excel OOXML Spreadsheet - Macro-Enabled", "application/vnd.ms-excel.sheet.macroEnabled.12", []],
70 | ["XLTM", "Excel OOXML Spreadsheet - Macro-Enabled Template", "application/vnd.ms-excel.template.macroEnabled.12", []],
71 | ["XLAM", "Excel OOXML Spreadsheet - Macro-Enabled Add-In", "application/vnd.ms-excel.addin.macroEnabled.12", []],
72 | ["DOTX", "Word OOXML - Template", "application/vnd.openxmlformats-officedocument.wordprocessingml.template", []],
73 | ["DOCM", "Word OOXML - Macro Enabled", "application/vnd.ms-word.document.macroEnabled.12", []],
74 | ["DOTM", "Word OOXML Template - Macro Enabled", "application/vnd.ms-word.template.macroEnabled.12", []],
75 | ["XLTX", "Excel OOXML Spreadsheet - Template", "application/vnd.openxmlformats-officedocument.spreadsheetml.template", []],
76 | ["POTX", "PowerPoint OOXML Presentation - Template", "application/vnd.openxmlformats-officedocument.presentationml.template", []],
77 | ["PPSX", "PowerPoint Open XML - Slide Show", "application/vnd.openxmlformats-officedocument.presentationml.slideshow", []],
78 | ["PPAM", "PowerPoint Open XML - Macro-Enabled Add-In", "application/vnd.ms-powerpoint.addin.macroEnabled.12", []],
79 | ["PPTM", "PowerPoint Open XML - Macro-Enabled", "application/vnd.ms-powerpoint.presentation.macroEnabled.12", []],
80 | ["POTM", "PowerPoint Open XML - Macro-Enabled Template", "application/vnd.ms-powerpoint.template.macroEnabled.12", []],
81 | ["PPSM", "PowerPoint Open XML - Macro-Enabled Slide Show", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", []]
82 | ]
--------------------------------------------------------------------------------
/src/db/types.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": [
3 | "string",
4 | "number",
5 | "integer",
6 | "boolean",
7 | "object",
8 | "array",
9 | "date",
10 | "time",
11 | "datetime",
12 | "year",
13 | "yearmonth",
14 | "duration",
15 | "geopoint",
16 | "geojson",
17 | "any"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | // Mount the ResourceEditor app explicitly
6 | // (make sure you call the function after it's loaded)
7 | function mountResourceEditorApp([elementId, config, resource] = ['root', {
8 | authToken: null,
9 | api: null,
10 | lfs: null,
11 | organizationId: null,
12 | datasetId: null,
13 | resourceId: null,
14 | }, {}]) {
15 | ReactDOM.render(
16 |
17 |
18 | ,
19 | document.getElementById(elementId)
20 | );
21 | };
22 |
23 |
24 | // Automatically mount the app if an element with id='ResourceEditor' exists
25 | const element = document.getElementById('ResourceEditor');
26 | if (element) {
27 | const config = {
28 | datasetId: element.getAttribute('data-dataset-id'),
29 | api: element.getAttribute('data-api'),
30 | lfs: element.getAttribute('data-lfs'),
31 | authToken: element.getAttribute('data-auth-token'),
32 | organizationId: element.getAttribute('data-organization-id'),
33 | resourceId: element.getAttribute('data-resource-id')
34 | }
35 |
36 | ReactDOM.render(
37 |
38 |
42 | ,
43 | element
44 | );
45 | }
46 |
47 |
48 | export { ResourceEditor } from './App';
49 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 | import { configure } from 'enzyme';
7 | import Adapter from 'enzyme-adapter-react-16';
8 | configure({ adapter: new Adapter() });
9 |
--------------------------------------------------------------------------------
/src/utils/Utils.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { getFileExtension, onFormatTitle, onFormatName, onFormatBytes, removeHyphen } from './index'
4 |
5 | describe("Utils", () => {
6 |
7 | it('format size in Bytes, KB, MB, GB', () => {
8 |
9 | expect(onFormatBytes(100)).toEqual('100 Bytes');
10 | expect(onFormatBytes(1000)).toEqual('1 KB');
11 | expect(onFormatBytes(1222222)).toEqual('1.2 MB');
12 | expect(onFormatBytes(1222222222)).toEqual('1.2 GB');
13 | });
14 |
15 | it('format title', () => {
16 |
17 | expect(onFormatTitle("sample-data.csv")).toEqual('sample data');
18 | expect(onFormatTitle("sample_data.csv")).toEqual('sample data');
19 | expect(onFormatTitle("sample-data_csv.csv")).toEqual('sample data csv');
20 | expect(onFormatTitle("sampleData.csv")).toEqual('sampleData');
21 | });
22 |
23 | it('get file extension', () => {
24 |
25 | expect(getFileExtension("sample.csv")).toEqual('csv');
26 | expect(getFileExtension("sample.html")).toEqual('html');
27 | expect(getFileExtension("sample.xls")).toEqual('xls');
28 | expect(getFileExtension("sampleData.doc")).toEqual('doc');
29 | });
30 |
31 | it('format name', () => {
32 |
33 | expect(onFormatName("sample.csv")).toEqual('sample');
34 | expect(onFormatName("sample_data.html")).toEqual('sample_data');
35 | expect(onFormatName("sample-data.xls")).toEqual('sample-data');
36 | expect(onFormatName("sample-Data.doc")).toEqual('sample-Data');
37 | });
38 |
39 | it('remove hyphen from uuid', () => {
40 |
41 | expect(removeHyphen("fd77e419-32ae-4025-8d14-890343b605a3")).toEqual('fd77e41932ae40258d14890343b605a3');
42 | expect(removeHyphen("9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d")).toEqual('9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d');
43 | expect(removeHyphen("should not broken without hyphen")).toEqual('should not broken without hyphen');
44 | });
45 | });
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | const getFileExtension = (filename) => {
2 | return /[.]/.exec(filename) ? /[^.]+$/.exec(filename)[0] : undefined;
3 | };
4 |
5 | const onFormatTitle = (name) => {
6 | return name
7 | .replace(/\.[^/.]+$/, "")
8 | .replace(/_/g, " ")
9 | .replace(/-/g, " ");
10 | };
11 |
12 | const onFormatName = (name) => {
13 | return name.replace(/\.[^/.]+$/, "");
14 | };
15 |
16 | const onFormatBytes = (bytes, decimals = 1) => {
17 | if (bytes === 0) return "0 Bytes";
18 |
19 | const k = 1000;
20 | const dm = decimals < 0 ? 0 : decimals;
21 | const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
22 |
23 | const i = Math.floor(Math.log(bytes) / Math.log(k));
24 |
25 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
26 | };
27 |
28 | const removeHyphen = (id) => {
29 | return id.replace(/-/g, "");
30 | };
31 |
32 | export {
33 | getFileExtension,
34 | onFormatTitle,
35 | onFormatName,
36 | onFormatBytes,
37 | removeHyphen,
38 | }
--------------------------------------------------------------------------------
/stories/1-app..stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '../src/App';
3 |
4 | export default {
5 | title: 'Templates',
6 | component: App,
7 | };
8 |
9 | export const AppTemplate = () => ;
10 |
--------------------------------------------------------------------------------
/stories/2-uploader.stories.js:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 |
4 | import Upload from '../src/components/Upload'
5 | import '../src/App.css';
6 |
7 | export default {
8 | title: 'Components /Upload',
9 | component: Upload,
10 | };
11 |
12 |
13 | export const Idle = () =>
;
14 |
15 |
--------------------------------------------------------------------------------
/stories/3-metadata.stories.js:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { action } from '@storybook/addon-actions';
4 |
5 | import Metadata from '../src/components/Metadata'
6 |
7 | export default {
8 | title: 'Components /Metadata',
9 | component: Metadata,
10 | argTypes: {
11 | loading: { control: 'boolean' }
12 | }
13 | }
14 |
15 | const Template = (args) => ( );
16 |
17 | export const Idle = Template.bind({});
18 |
19 | Idle.args = {
20 | metadata: {
21 | title: "",
22 | description: "",
23 | encoding: "",
24 | path: "",
25 | format: "",
26 | license: ""
27 | },
28 | handleChange: action('change value'),
29 | handleSubmit: action('submit form'),
30 | uploadSuccess: false
31 | };
32 |
33 |
34 | export const Loading = Template.bind({});
35 |
36 | Loading.args = {
37 | ...Idle.args,
38 | };
39 |
40 | export const Success = Template.bind({});
41 |
42 | Success.args = {
43 | ...Idle.args,
44 | uploadSuccess: true
45 | };
46 |
47 |
--------------------------------------------------------------------------------
/stories/4-TableSchema.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { action } from "@storybook/addon-actions";
3 |
4 | import TableSchema from "../src/components/TableSchema";
5 |
6 | export default {
7 | title: "Components /TableSchema",
8 | component: TableSchema,
9 | };
10 |
11 | const Template = (args) => ;
12 |
13 | export const Idle = Template.bind({});
14 |
15 | Idle.args = {
16 | schema: {
17 | fields: [
18 | {
19 | title: "",
20 | name: "name",
21 | type: "string",
22 | description: "",
23 | format: "",
24 | },
25 | {
26 | title: "",
27 | name: "age",
28 | type: "integer",
29 | description: "",
30 | format: "",
31 | },
32 | {
33 | title: "",
34 | name: "address",
35 | type: "string",
36 | description: "",
37 | format: "",
38 | },
39 | ],
40 | },
41 | data: [
42 | { name: "Eathan Pritchard", age: 25, address: "1201 Tompkins Dr Madison" },
43 | { name: "Zidan Berg", age: 22, address: "1309 Tompkins Dr Madison" },
44 | { name: "Raisa Kinney", age: 32, address: "1497 Tompkins Dr Madison" },
45 | { name: "Cara Woodley", age: 30, address: "1197 Buckeye Rd Madison" },
46 | { name: "Komal Robbins", age: 42, address: "1192 Buckeye Rd Madison" },
47 | { name: "Deacon Childs", age: 28, address: "1027 Tompkins Dr Madison" },
48 | { name: "Ayse Shaw", age: 21, address: "1233 Buckeye Rd Madison" },
49 | ],
50 | };
51 |
52 |
53 | export const Loading = Template.bind({});
54 |
55 | Loading.args = {
56 | ...Idle.args,
57 | };
58 |
59 | export const Success = Template.bind({});
60 |
61 | Success.args = {
62 | ...Idle.args,
63 | uploadSuccess: true
64 | };
65 |
--------------------------------------------------------------------------------
/stories/5-InputFile.stories.js:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { action } from '@storybook/addon-actions';
4 |
5 | import InputFile from '../src/components/InputFile'
6 |
7 | export default {
8 | title: 'Components /Input/File',
9 | component: InputFile,
10 | }
11 |
12 | const Template = (args) => (
13 |
14 |
15 |
16 | );
17 |
18 | export const DragAndDrop = Template.bind({});
19 |
20 | DragAndDrop.args = {
21 | onChangeHandler: action('change value'),
22 | };
23 |
--------------------------------------------------------------------------------
/stories/6-InputUrl.stories.js:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { action } from '@storybook/addon-actions';
4 |
5 | import InputUrl from '../src/components/InputUrl'
6 |
7 | export default {
8 | title: 'Components /Input/Url',
9 | component: InputUrl,
10 | }
11 |
12 | const Template = (args) => (
13 |
14 |
15 |
16 | );
17 |
18 | export const Default = Template.bind({});
19 |
20 | Default.args = {
21 | onChangeUrl: action('change value'),
22 | };
23 |
--------------------------------------------------------------------------------
/stories/7-Choose.stories.js:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { action } from '@storybook/addon-actions';
4 |
5 | import Choose from '../src/components/Choose'
6 |
7 | export default {
8 | title: 'Components /Choose ',
9 | component: Choose,
10 | }
11 |
12 | const Template = (args) => (
13 |
14 |
15 |
16 | );
17 |
18 | export const Default = Template.bind({});
19 |
20 | Default.args = {
21 | onChangeUrl: action('change url'),
22 | onChangeHandler: action('change file'),
23 | };
24 |
--------------------------------------------------------------------------------