From 2d0c33c024055d8a16b3d4dae67334b4f46c40a2 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Wed, 13 Mar 2019 18:21:09 +0200 Subject: [PATCH] Add drag and drop --- package-lock.json | 122 ++++++++++++++++++ package.json | 1 + .../DatetimeWidget/DatetimeWidget.tsx | 21 ++- .../SignupQuestionsWidget/QuestionList.tsx | 69 ++++++++++ .../SignupQuestionsWidget.scss | 2 + .../SignupQuestionsWidget.tsx | 54 ++++---- src/pages/EventCreatePage/EventCreatePage.tsx | 8 +- src/pages/FeedCreatePage/FeedCreatePage.tsx | 8 +- .../SignupCreatePage/SignupCreatePage.tsx | 10 +- 9 files changed, 256 insertions(+), 39 deletions(-) create mode 100644 src/components/SignupQuestionsWidget/QuestionList.tsx diff --git a/package-lock.json b/package-lock.json index a6cf07f..ba84a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -153,6 +153,37 @@ "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==", "dev": true }, + "@babel/runtime": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz", + "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==", + "requires": { + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "@babel/runtime-corejs2": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.3.4.tgz", + "integrity": "sha512-QwPuQE65kNxjsNKk34Rfgen2R5fk0J2So99SD45uXBp34QOfyz11SqVgJ4xvyCpnCIieSQ0X0hSSc9z/ymlJJw==", + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, "@babel/template": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", @@ -4212,6 +4243,14 @@ } } }, + "css-box-model": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.1.1.tgz", + "integrity": "sha512-ZxbuLFeAPEDb0wPbGfT7783Vb00MVAkvOlMKwr0kA2PD5EGxk6P3MAhedvVuyVJCWb54bb+6HQ7pdPYENf8AZw==", + "requires": { + "tiny-invariant": "^1.0.3" + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -9999,6 +10038,11 @@ "mimic-fn": "^1.0.0" } }, + "memoize-one": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", + "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -13276,6 +13320,11 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "raf-schd": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.0.tgz", + "integrity": "sha512-m7zq0JkIrECzw9mO5Zcq6jN4KayE34yoIS9hJoiZNXyOAT06PPA8PrR+WtJIeFW09YjUfNkMMN9lrmAt6BURCA==" + }, "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", @@ -13404,6 +13453,21 @@ "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=", "dev": true }, + "react-beautiful-dnd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-10.1.0.tgz", + "integrity": "sha512-oij2ZLIQ6SFmqy/MiXbuO8HUFHQ39gaPsTYj0Ewk0XwbLM32L+diVP0NXbEK7nhYv5lF2jviXGnda+BYMi6+nA==", + "requires": { + "@babel/runtime-corejs2": "^7.3.4", + "css-box-model": "^1.1.1", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.1", + "raf-schd": "^4.0.0", + "react-redux": "^5.0.7", + "redux": "^4.0.1", + "tiny-invariant": "^1.0.3" + } + }, "react-dom": { "version": "16.4.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.1.tgz", @@ -13441,6 +13505,11 @@ "shallowequal": "^1.0.2" } }, + "react-is": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==" + }, "react-jsonschema-form": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.2.0.tgz", @@ -13481,6 +13550,30 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-redux": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", + "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", + "requires": { + "@babel/runtime": "^7.1.2", + "hoist-non-react-statics": "^3.1.0", + "invariant": "^2.2.4", + "loose-envify": "^1.1.0", + "prop-types": "^15.6.1", + "react-is": "^16.6.0", + "react-lifecycles-compat": "^3.0.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } + } + } + }, "react-router": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", @@ -13730,6 +13823,30 @@ } } }, + "redux": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + } + } + }, "referrer-policy": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz", @@ -16735,6 +16852,11 @@ "setimmediate": "^1.0.4" } }, + "tiny-invariant": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.3.tgz", + "integrity": "sha512-ytQx8T4DL8PjlX53yYzcIC0WhIZbpR0p1qcYjw2pHu3w6UtgWwFJQ/02cnhOnBBhlFx/edUIfcagCaQSe3KMWg==" + }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", diff --git a/package.json b/package.json index c55e82f..a874b1f 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "mobx-react": "^5.2.3", "normalize.css": "^8.0.0", "query-string": "^6.2.0", + "react-beautiful-dnd": "^10.1.0", "react-helmet": "^5.2.0", "react-jsonschema-form": "^1.2.0", "react-router": "^4.3.1", diff --git a/src/components/DatetimeWidget/DatetimeWidget.tsx b/src/components/DatetimeWidget/DatetimeWidget.tsx index 51c79d6..36a7300 100644 --- a/src/components/DatetimeWidget/DatetimeWidget.tsx +++ b/src/components/DatetimeWidget/DatetimeWidget.tsx @@ -3,6 +3,8 @@ import * as React from "react"; export interface DatetimeWidgetProps { value: string; onChange: (value: string) => void; + onFocus: () => void; + onBlur: () => void; required: boolean; disabled: boolean; } @@ -10,7 +12,7 @@ export interface DatetimeWidgetState {} class DatetimeWidget extends React.Component { render() { - const { value, onChange, required, disabled } = this.props; + const { value, onChange, onFocus, onBlur, required, disabled } = this.props; let date, time; if (value && value.length !== 0) { @@ -19,20 +21,25 @@ class DatetimeWidget extends React.Component onChange(`${event.target.value}T${time}`)} - required={required} - disabled={disabled} - value={date} /> + value={date} + {...commonProps} /> onChange(`${date}T${event.target.value}:00`)} - required={required} - disabled={disabled} - value={time} /> + value={time} + {...commonProps} /> ); } diff --git a/src/components/SignupQuestionsWidget/QuestionList.tsx b/src/components/SignupQuestionsWidget/QuestionList.tsx new file mode 100644 index 0000000..367398d --- /dev/null +++ b/src/components/SignupQuestionsWidget/QuestionList.tsx @@ -0,0 +1,69 @@ +import * as React from "react"; +import { Fragment } from "react"; +import * as shortid from "shortid"; +import { Draggable } from "react-beautiful-dnd"; +import { Question, InputProps, optionTypes, SignupQuestionError } from "./index"; +import OptionsWidget from "./OptionsWidget"; +import TypeWidget from "./TypeWidget"; + +export interface QuestionListProps { + questions: Question[]; + innerRef: any; + placeholder: any; + onChange: (value: Question[]) => void; +} +export interface QuestionListState { } + +class QuestionList extends React.Component { + renderTextWidget = ({ questions, value, index }: InputProps) => ( + + ) + + handleNameInputChange = (questions: Question[], index: number) => (event) => { + const { onChange } = this.props; + const val = event.target.value; + questions[index].name = val; + onChange(questions); + } + + renderQuestions() { + const { questions, onChange, placeholder } = this.props; + return questions.map((q, index) => { + const nameWidgetProps = { value: q.name, type: "text", questions, index }; + const nameWidget = this.renderTextWidget(nameWidgetProps); + + const dataProps = { value: q.options, type: q.type, questions, index }; + const optionsWidget = ; + const typeSelectWidget = ; + return ( + + {(provided) => ( +
+ {nameWidget} + {typeSelectWidget} + {optionsWidget} +
+ )} +
+ ); + }); + } + + render() { + const { placeholder, innerRef } = this.props; + + return ( +
+ { this.renderQuestions() } + { placeholder } +
+ ); + } +} + +export default QuestionList; diff --git a/src/components/SignupQuestionsWidget/SignupQuestionsWidget.scss b/src/components/SignupQuestionsWidget/SignupQuestionsWidget.scss index 0d5329f..48488bd 100644 --- a/src/components/SignupQuestionsWidget/SignupQuestionsWidget.scss +++ b/src/components/SignupQuestionsWidget/SignupQuestionsWidget.scss @@ -11,5 +11,7 @@ margin-bottom: 1rem; display: flex; flex-flow: column nowrap; + padding: 0.5rem; + background-color: $green; } } diff --git a/src/components/SignupQuestionsWidget/SignupQuestionsWidget.tsx b/src/components/SignupQuestionsWidget/SignupQuestionsWidget.tsx index 4639e85..f08db6f 100644 --- a/src/components/SignupQuestionsWidget/SignupQuestionsWidget.tsx +++ b/src/components/SignupQuestionsWidget/SignupQuestionsWidget.tsx @@ -1,15 +1,18 @@ import * as React from "react"; import * as shortid from "shortid"; import OptionsWidget from "./OptionsWidget"; +import { DragDropContext, Droppable } from "react-beautiful-dnd"; import { Question, InputProps, optionTypes } from "."; // @ts-ignore import * as AddIcon from "../../assets/img/add-icon.png"; import "./SignupQuestionsWidget.scss"; import TypeWidget from "./TypeWidget"; +import QuestionList from "./QuestionList"; export interface SignupQuestionsWidgetProps { value: string; onChange: (value: string) => void; + onFocus: () => void; required: boolean; disabled: boolean; } @@ -34,42 +37,39 @@ class SignupQuestionsWidget extends React.Component (event) => { - const val = event.target.value; - questions[index].name = val; + handleDragEnd = (questions: Question[]) => (result) => { + const srcIndex = result.source.index; + const dstIndex = result.destination.index; + const srcCopy = { ...questions[srcIndex] }; + const dstCopy = { ...questions[dstIndex] }; + questions[dstIndex] = srcCopy; + questions[srcIndex] = dstCopy; + this.onValueChange(questions); } - renderTextWidget = ({ questions, value, index }: InputProps) => ( - - ) - - renderQuestions = (questions: Question[]) => { - return questions.map((q, index) => { - const nameWidgetProps = { value: q.name, type: "text", questions, index }; - const nameWidget = this.renderTextWidget(nameWidgetProps); - - const dataProps = { value: q.options, type: q.type, questions, index }; - const optionsWidget = ; - const typeSelectWidget = ; - return ( -
- { nameWidget } - { typeSelectWidget } - { optionsWidget } -
- ); - }); - } - render() { - const { value } = this.props; + const { value, onFocus } = this.props; const questions = JSON.parse(value) as Question[]; return (
- {this.renderQuestions(questions)} + + + {(provided) => ( + + )} + + diff --git a/src/pages/EventCreatePage/EventCreatePage.tsx b/src/pages/EventCreatePage/EventCreatePage.tsx index d78b228..96c7cdd 100644 --- a/src/pages/EventCreatePage/EventCreatePage.tsx +++ b/src/pages/EventCreatePage/EventCreatePage.tsx @@ -113,6 +113,11 @@ class EventCreatePage extends React.Component { this.setState({ formData: data.formData, + }); + } + + onFocus = () => { + this.setState({ statusMessage: null, }); } @@ -220,7 +225,8 @@ class EventCreatePage extends React.Component + onError={this.onError} + onFocus={this.onFocus} /> { error &&
{error}
}
); diff --git a/src/pages/FeedCreatePage/FeedCreatePage.tsx b/src/pages/FeedCreatePage/FeedCreatePage.tsx index 00d9eca..7f59d2d 100644 --- a/src/pages/FeedCreatePage/FeedCreatePage.tsx +++ b/src/pages/FeedCreatePage/FeedCreatePage.tsx @@ -112,6 +112,11 @@ class FeedCreatePage extends React.Component { this.setState({ formData: data.formData, + }); + } + + onFocus = () => { + this.setState({ statusMessage: null, }); } @@ -218,7 +223,8 @@ class FeedCreatePage extends React.Component + onError={this.onError} + onFocus={this.onFocus} /> { error &&
{error}
} ); diff --git a/src/pages/SignupCreatePage/SignupCreatePage.tsx b/src/pages/SignupCreatePage/SignupCreatePage.tsx index 2d264fb..e00b3d7 100644 --- a/src/pages/SignupCreatePage/SignupCreatePage.tsx +++ b/src/pages/SignupCreatePage/SignupCreatePage.tsx @@ -1,9 +1,7 @@ import * as React from "react"; import Helmet from "react-helmet"; import "./SignupCreatePage.scss"; -import { isAuthenticated } from "../../auth"; import Form from "react-jsonschema-form"; -import { Tag, getTags } from "../../models/Tag"; import { createForm, getForm, updateForm } from "../../models/SignupForm"; import DatetimeWidget from "../../components/DatetimeWidget"; import SignupQuestionsWidget from "../../components/SignupQuestionsWidget"; @@ -91,6 +89,11 @@ class SignupCreatePage extends React.Component { this.setState({ formData: data.formData, + }); + } + + onFocus = () => { + this.setState({ statusMessage: null, }); } @@ -180,7 +183,8 @@ class SignupCreatePage extends React.Component + onError={this.onError} + onFocus={this.onFocus} /> { error &&
{error}
} );