Commit 28a81952 authored by Paul Colin Hennig's avatar Paul Colin Hennig
Browse files

add edit locations

parent 8f930b5c
......@@ -23,6 +23,7 @@ import Map from "./components/map/Map";
import Login from "./components/login/Login";
import TwoFa from "./components/twoFa/TwoFa";
import TwoFaLogin from "./components/login/twoFa/TwoFaLogin";
import EditLocation from "./components/locations/edit/EditLocation";
export default class App extends Component {
state = {
db: null,
......@@ -79,6 +80,8 @@ export default class App extends Component {
const events = await db.events.toArray();
const locations = await db.locations.toArray();
const templateEvents = await db.templateEvents.toArray();
const templateLocations = await db.templateLocations.toArray();
const settings = this.arrayToObject(await db.settings.toArray());
new SignalClient(db);
......@@ -86,7 +89,7 @@ export default class App extends Component {
settings.theme = 0;
}
this.setState({ db, events, settings, templateEvents, locations });
this.setState({ db, events, settings, templateEvents, locations, templateLocations });
};
changeListType = type => {
......@@ -162,6 +165,34 @@ export default class App extends Component {
.then(updated => {
if (!updated) this.state.db.templateEvents.put(dbData);
});
} else if (kind === "location") {
const location = item;
const time = Date.now();
this.setState(({ templateLocations }) => {
templateLocations = templateLocations.map(e => {
if (e.id === location.id) {
if (!location.private) location.private = {};
location.private.lastChange = time;
return location;
}
return e;
});
return { templateLocations };
});
const dbData = cloneDeep(location);
delete dbData.public.relatedEventsData;
if (!dbData.private) dbData.private = {};
dbData.private.lastChange = time;
this.state.db.templateLocations
.update(location.id, {
public: dbData.public,
private: dbData.private
})
.then(updated => {
if (!updated) this.state.db.templateLocations.put(dbData);
});
}
};
......@@ -176,6 +207,16 @@ export default class App extends Component {
});
return { templateEvents };
});
} else if (kind === "location") {
const location = item;
this.state.db.templateLocations.delete(location.id);
this.setState(({ templateLocations }) => {
templateLocations = templateLocations.filter(e => {
return e.id !== location.id;
});
return { templateLocations };
});
}
};
......@@ -191,6 +232,17 @@ export default class App extends Component {
});
return { templateEvents: cloneDeep(templateEvents) };
});
} else if (kind === "location") {
this.setState(({ templateLocations }) => {
const newTemplateLocation = this.state.locations.filter(location => {
return id === location.id;
});
templateLocations.push({
id: newTemplateLocation[0].id,
public: newTemplateLocation[0].public
});
return { templateLocations: cloneDeep(templateLocations) };
});
}
};
......@@ -348,6 +400,34 @@ export default class App extends Component {
);
}}
></Route>
<Route
exact
path="/locations/:locationId/edit"
render={routeProps => {
if (!this.state.db) {
return <div />;
}
const location = this.state.templateLocations.filter(location => {
if (location.public.id === routeProps.match.params.locationId) return location;
return false;
})[0];
if (location) {
location.public.relatedEventsData = this.state.events.filter(event => {
return event.public.locationId === location.id;
});
}
return (
<EditLocation
{...routeProps}
location={location}
updateTemplateItem={this.updateTemplateItem}
resetTemplateItem={this.resetTemplateItem}
createTemplateItem={this.createTemplateItem}
></EditLocation>
);
}}
></Route>
<Route exact path="/settings">
<Settings />
<Menu></Menu>
......
.EditLocation {
box-sizing: border-box;
}
.EditLocation header a.back {
padding-right: 0;
box-sizing: border-box;
width: 33%;
}
.EditLocation header h2 {
display: inline-block;
margin: 0;
padding: 0;
line-height: 1em;
text-align: left;
vertical-align: -5px;
font-size: 14px;
width: 60%;
box-sizing: border-box;
}
.EditLocation header button {
margin: 0px;
height: 100%;
width: 33%;
float: left;
box-sizing: border-box;
}
.EditLocation header .reset .icon,
.EditLocation header .publish .icon {
display: inline-block;
width: 20px;
padding-right: 10px;
padding-top: 15px;
}
.EditLocation header button .publish h2 {
display: inline-block;
width: 30px;
vertical-align: -10px;
}
import { faChevronLeft, faCoins, faMinusCircle, faPaintRoller, faPlusCircle, faTrashAlt, faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { Component } from "react";
import { Link } from "react-router-dom";
import "./EditLocation.css";
import "../../events/singleEvent/SingleEvent.css";
import MetaTags, { timestampToReadable } from "../../metaTags/MetaTags";
import RelatedEvents from "../../events/components/relatedEvents/RelatedEvents";
import { definitiveDescription } from "../../list/itemSingle/ItemSingle";
export default class EditLocation extends Component {
state = {
dnt: false
};
componentDidMount() {
document.getElementById("root").scrollTo(0, 0);
if (!this.props.location) this.props.createTemplateItem(this.props.match.params.locationId, "location");
}
componentDidUpdate = () => {
if (!this.props.location) this.props.createTemplateItem(this.props.match.params.locationId, "location");
};
handleUpdate = (e, i) => {
const t = e.target?.textContent;
const k = this.props.location;
if (e.target?.localName === "h1") {
k.public.title = t;
} else if (e.target?.localName === "h2") {
k.public.subTitle = t;
} else if (e.target?.localName === "p") {
k.public.description = t;
} else if (e.target?.localName === "p") {
k.public.description = t;
} else if (e.target?.classList[0] === "colorPicker") {
k.public.colorTheme = e.target.value.substr(1);
} else if (e.target?.classList[0] === "long") {
k.public.location.long = t;
} else if (e.target?.classList[0] === "lat") {
k.public.location.lat = t;
} else if (e === "removePriceOption") {
k.public.prices = k.public.prices.filter((e, i2) => {
return i2 !== i;
});
} else if (e === "addPriceOption") {
k.public.prices.push({
label: "Option",
price: "10"
});
} else if (e.target?.classList[0] === "pricePrice") {
k.public.prices = k.public.prices.map((e2, i2) => {
if (i === i2) {
if (t === "Donation" || t === "donation" || t === "DNT" || t === "dnt") {
return { label: e2.label, price: "DNT" };
} else {
return { label: e2.label, price: t.replaceAll("", "") };
}
}
return e2;
});
} else if (e.target?.classList[0] === "priceLabel") {
k.public.prices = k.public.prices.map((e2, i2) => {
if (i === i2) {
return { label: t, price: e2.price };
}
return e2;
});
}
this.props.updateTemplateItem(this.props.location, "location");
};
render() {
return <div></div>;
const header = () => {
return (
<header>
<Link className="back" draggable="false" exact="true" to={`/locations/${this.props.match.params.locationId}`}>
<FontAwesomeIcon className="arrow" icon={faChevronLeft} />
<h2>Save & Back</h2>
</Link>
<button onClick={() => this.props.resetTemplateItem(this.props.location, "location")} className="reset">
<FontAwesomeIcon className="icon" icon={faTrashAlt} />
<h2>Reset Template</h2>
</button>
<button className="publish">
<FontAwesomeIcon className="icon" icon={faUpload} />
<h2>Publish Location</h2>
</button>
</header>
);
};
if (!this.props.location) {
return <div className="EditLocation SingleEvent EditEvent">{header()}</div>;
}
const l = this.props.location.public;
const bl = {
borderLeft: `2px solid ${l.colorTheme ? "#" + l.colorTheme : "var(--f1)"}`
};
return (
<div className="EditLocation SingleEvent EditEvent">
{header()}
<div className="body">
{this.props.event?.private?.lastChange ? (
<div className="lastEdit">
Last edit on
{" " + timestampToReadable(this.props.event.private.lastChange)}
</div>
) : (
""
)}
<div className="ItemSingle">
<div className="head">
<h1 suppressContentEditableWarning={true} contentEditable="true" placeholder="Title" onBlur={this.handleUpdate}>
{l.title}
</h1>
{l.subTitle ? (
<h2 suppressContentEditableWarning={true} contentEditable="true" placeholder="Subtitle (optional)" onBlur={this.handleUpdate}>
{l.subTitle}
</h2>
) : (
""
)}
</div>
<img src={l.imageId ? `/i/${l.imageId}.jpg` : ""} width="2" height="1" alt="" draggable="false" />
<MetaTags kind="event" item={l}></MetaTags>
<p suppressContentEditableWarning={true} contentEditable="true" placeholder="Description" onBlur={this.handleUpdate} style={{ maxHeight: "initial" }}>
{definitiveDescription({ ...l }, 1)}
</p>
</div>
<div className="details">
{l.prices?.length ? (
<div style={bl} className="price dItem">
<h2>
<FontAwesomeIcon className="icon" icon={faCoins} /> Price
</h2>
<div>
<ul>
{l.prices.map((price, i) => {
return (
<li key={i}>
<span
suppressContentEditableWarning={true}
contentEditable="true"
placeholder="Label"
className="priceLabel"
onKeyPress={this.handleKeypress}
onBlur={e => this.handleUpdate(e, i)}
>
{price.label}
</span>
-
<span
suppressContentEditableWarning={true}
contentEditable="true"
placeholder="Price"
className="pricePrice"
onKeyPress={this.handleKeypress}
onBlur={e => this.handleUpdate(e, i)}
>
{price.price === "DNT" ? "Donation" : price.price}
</span>
{price.price.length && price.price !== "DNT" ? "" : ""}
{i === 0 ? (
""
) : (
<button className="removePriceOption" onClick={() => this.handleUpdate("removePriceOption", i)}>
<FontAwesomeIcon className="icon" icon={faMinusCircle} />
</button>
)}
</li>
);
})}
</ul>
{l.prices.length < 10 ? (
<button className="addPriceOption" onClick={() => this.handleUpdate("addPriceOption")}>
<FontAwesomeIcon className="icon" icon={faPlusCircle} />
</button>
) : (
""
)}
</div>
</div>
) : (
""
)}
</div>
<div className="related">
<h2>Related Events</h2>
<RelatedEvents event={l} edit={false}></RelatedEvents>
</div>
</div>
<div className="kls">
<div style={bl} className="themeColor">
<h2>
<FontAwesomeIcon className="icon" icon={faPaintRoller} /> Theme Color
</h2>
<input value={l.colorTheme ? "#" + l.colorTheme : "#000000"} onChange={this.handleUpdate} className="colorPicker" type="color" />
</div>
<br />
<div style={bl} className="eventId infoBox">
<h2>Location Id (read only)</h2>
<h2 className="s">{l.id}</h2>
</div>
</div>
</div>
);
}
}
import React, { Component } from "react";
import { library, config } from "@fortawesome/fontawesome-svg-core";
import {
faCoins,
faMapMarkerAlt,
faClock,
faTag,
} from "@fortawesome/free-solid-svg-icons";
import { faCoins, faMapMarkerAlt, faClock, faTag } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import "../../css/svg-with-js.css";
import "./MetaTags.css";
......@@ -14,121 +9,130 @@ config.autoAddCss = false;
library.add(faCoins, faMapMarkerAlt, faClock, faTag);
export default class MetaTags extends Component {
render() {
const e = this.props.item;
const bb = {
borderBottom: `2px solid ${
e.colorTheme ? "#" + e.colorTheme : "var(--f1)"
}`,
};
if (this.props.kind === "event") {
return (
<div className="MetaTags">
<div className="meta">
<div className="innerMeta">
<span style={bb} className="time">
<FontAwesomeIcon className="icon" icon={faClock} />
{timestampToReadable(e.timeStart)}
{e.timeEnd ? "-" + timestampToReadable(e.timeEnd, 1) : ""}
</span>
<span style={bb} className="location">
<FontAwesomeIcon className="icon" icon={faMapMarkerAlt} />
{e.location.name}
</span>
<span style={bb} className="price">
<FontAwesomeIcon className="icon" icon={faCoins} />
{getPrices(e.prices)}
</span>
{e.keywords.slice(0, 3).map((e, i) => {
return (
<span key={i} style={bb}>
<FontAwesomeIcon className="icon" icon={faTag} />
{e}
</span>
);
})}
</div>
</div>
</div>
);
} else {
return (
<div className="MetaTags">
<div className="meta">
<div className="innerMeta">
{e.keywords.slice(0, 3).map((e, i) => {
return (
<span key={i} style={bb}>
<FontAwesomeIcon className="icon" icon={faTag} />
{e}
</span>
);
})}
</div>
</div>
</div>
);
render() {
const e = this.props.item;
const bb = {
borderBottom: `2px solid ${e.colorTheme ? "#" + e.colorTheme : "var(--f1)"}`
};
if (this.props.kind === "event") {
return (
<div className="MetaTags">
<div className="meta">
<div className="innerMeta">
{e.timeStart ? (
<span style={bb} className="time">
<FontAwesomeIcon className="icon" icon={faClock} />
{timestampToReadable(e.timeStart)}
{e.timeEnd ? "-" + timestampToReadable(e.timeEnd, 1) : ""}
</span>
) : (
""
)}
{e.location && e.location.name ? (
<span style={bb} className="location">
<FontAwesomeIcon className="icon" icon={faMapMarkerAlt} />
{e.location?.name}
</span>
) : (
""
)}
{e.prices && e.prices.length ? (
<span style={bb} className="price">
<FontAwesomeIcon className="icon" icon={faCoins} />
{getPrices(e.prices)}
</span>
) : (
""
)}
{e.keywords && e.keywords.length
? e.keywords.slice(0, 3).map((e, i) => {
return (
<span key={i} style={bb}>
<FontAwesomeIcon className="icon" icon={faTag} />
{e}
</span>
);
})
: ""}
</div>
</div>
</div>
);
} else {
return (
<div className="MetaTags">
<div className="meta">
<div className="innerMeta">
{e.keywords.slice(0, 3).map((e, i) => {
return (
<span key={i} style={bb}>
<FontAwesomeIcon className="icon" icon={faTag} />
{e}
</span>
);
})}
</div>
</div>
</div>
);
}
}
}
}
export const timestampToReadable = (timeStamp, mode = 0) => {
const userLang = navigator.language || navigator.userLanguage;
const userLang = navigator.language || navigator.userLanguage;
const date =
timeStamp || timeStamp === 0
? new Date(parseInt(timeStamp))
: new Date(parseInt(Date.now()));
if (mode === 0) {
const fm = new Intl.DateTimeFormat(userLang, {
day: "numeric",
minute: "numeric",
hour: "numeric",
weekday: "short",
month: "numeric",
year: "2-digit",
});
return fm.format(date).replaceAll(",", "");
} else if (mode === 1) {
const fm = new Intl.DateTimeFormat(userLang, {
minute: "numeric",
hour: "numeric",
});
return fm.format(date).replaceAll(",", "");
} else if (mode === 2) {
const fm = new Intl.DateTimeFormat(userLang, {
day: "numeric",
minute: "numeric",
hour: "numeric",
weekday: "long",
month: "numeric",
year: "numeric",
});
return fm.format(date).replaceAll(",", "");
}
const date = timeStamp || timeStamp === 0 ? new Date(parseInt(timeStamp)) : new Date(parseInt(Date.now()));
if (mode === 0) {
const fm = new Intl.DateTimeFormat(userLang, {
day: "numeric",
minute: "numeric",
hour: "numeric",
weekday: "short",
month: "numeric",
year: "2-digit"
});
return fm.format(date).replaceAll(",", "");
} else if (mode === 1) {
const fm = new Intl.DateTimeFormat(userLang, {
minute: "numeric",
hour: "numeric"
});
return fm.format(date).replaceAll(",", "");
} else if (mode === 2) {
const fm = new Intl.DateTimeFormat(userLang, {
day: "numeric",
minute: "numeric",
hour: "numeric",
weekday: "long",
month: "numeric",
year: "numeric"
});
return fm.format(date).replaceAll(",", "");
}
};