Add drag and drop

This commit is contained in:
Jan Tuomi
2019-03-13 18:21:09 +02:00
parent c0168c6a14
commit 2d0c33c024
9 changed files with 256 additions and 39 deletions
+122
View File
@@ -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",
+1
View File
@@ -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",
@@ -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<DatetimeWidgetProps, DatetimeWidgetState> {
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<DatetimeWidgetProps, DatetimeWidget
time = rest.slice(0, 5);
}
const commonProps = {
onFocus,
onBlur,
required,
disabled,
};
return (
<div className="datetime-widget">
<input
type="date"
onChange={(event) => onChange(`${event.target.value}T${time}`)}
required={required}
disabled={disabled}
value={date} />
value={date}
{...commonProps} />
<input
type="time"
onChange={(event) => onChange(`${date}T${event.target.value}:00`)}
required={required}
disabled={disabled}
value={time} />
value={time}
{...commonProps} />
</div>
);
}
@@ -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<QuestionListProps, QuestionListState> {
renderTextWidget = ({ questions, value, index }: InputProps) => (
<input type="text" value={value} onChange={this.handleNameInputChange(questions, index)} />
)
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 = <OptionsWidget inputProps={dataProps} onChange={onChange} />;
const typeSelectWidget = <TypeWidget inputProps={dataProps} onChange={onChange} />;
return (
<Draggable draggableId={q.id} key={q.id} index={index}>
{(provided) => (
<div
className="signup-questions-widget-row"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{nameWidget}
{typeSelectWidget}
{optionsWidget}
</div>
)}
</Draggable>
);
});
}
render() {
const { placeholder, innerRef } = this.props;
return (
<div ref={innerRef}>
{ this.renderQuestions() }
{ placeholder }
</div>
);
}
}
export default QuestionList;
@@ -11,5 +11,7 @@
margin-bottom: 1rem;
display: flex;
flex-flow: column nowrap;
padding: 0.5rem;
background-color: $green;
}
}
@@ -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<SignupQuestionsWidgetProps,
this.onValueChange(newQuestions);
}
handleNameInputChange = (questions: Question[], index: number) => (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) => (
<input type="text" value={value} onChange={this.handleNameInputChange(questions, index)} />
)
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 = <OptionsWidget inputProps={dataProps} onChange={this.onValueChange} />;
const typeSelectWidget = <TypeWidget inputProps={dataProps} onChange={this.onValueChange} />;
return (
<div key={q.id} className="signup-questions-widget-row">
{ nameWidget }
{ typeSelectWidget }
{ optionsWidget }
</div>
);
});
}
render() {
const { value } = this.props;
const { value, onFocus } = this.props;
const questions = JSON.parse(value) as Question[];
return (
<div className="signup-questions-widget">
{this.renderQuestions(questions)}
<DragDropContext
onDragEnd={this.handleDragEnd(questions)}
onDragStart={onFocus}
>
<Droppable droppableId="questions">
{(provided) => (
<QuestionList
{...provided.droppableProps}
innerRef={provided.innerRef}
questions={questions}
onChange={this.onValueChange}
placeholder={provided.placeholder} />
)}
</Droppable>
</DragDropContext>
<button type="button" className="add-link" onClick={this.handleNewRowClick(questions)}>
<img src={AddIcon} /> New Question
</button>
@@ -113,6 +113,11 @@ class EventCreatePage extends React.Component<EventCreatePageProps, EventCreateP
onChange = (data) => {
this.setState({
formData: data.formData,
});
}
onFocus = () => {
this.setState({
statusMessage: null,
});
}
@@ -220,7 +225,8 @@ class EventCreatePage extends React.Component<EventCreatePageProps, EventCreateP
widgets={widgets}
onChange={this.onChange}
onSubmit={this.onSubmit}
onError={this.onError} />
onError={this.onError}
onFocus={this.onFocus} />
{ error && <div className="error">{error}</div> }
</div>
);
+7 -1
View File
@@ -112,6 +112,11 @@ class FeedCreatePage extends React.Component<FeedCreatePageProps, FeedCreatePage
onChange = (data) => {
this.setState({
formData: data.formData,
});
}
onFocus = () => {
this.setState({
statusMessage: null,
});
}
@@ -218,7 +223,8 @@ class FeedCreatePage extends React.Component<FeedCreatePageProps, FeedCreatePage
widgets={widgets}
onChange={this.onChange}
onSubmit={this.onSubmit}
onError={this.onError} />
onError={this.onError}
onFocus={this.onFocus} />
{ error && <div className="error">{error}</div> }
</div>
);
@@ -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<SignupCreatePageProps, SignupCrea
onChange = (data) => {
this.setState({
formData: data.formData,
});
}
onFocus = () => {
this.setState({
statusMessage: null,
});
}
@@ -180,7 +183,8 @@ class SignupCreatePage extends React.Component<SignupCreatePageProps, SignupCrea
widgets={widgets}
onChange={this.onChange}
onSubmit={this.onSubmit}
onError={this.onError} />
onError={this.onError}
onFocus={this.onFocus} />
{ error && <div className="error">{error}</div> }
</div>
);