+ {field.value.map((media, idx) => {
+ return renderMedia({ media, idx });
+ })}
+
+ );
+ case "line":
+ return (
+
+
+ {field.title &&
{field.title} }
+
+ {field.value.map((t, idx) => (
+
+ ))}
+
+
+ ) : null;
+ default:
+ return null;
+ }
+ }
+
+ function renderRow(row) {
+ return (
+
+ {row.map((field) => (
+ {renderField(field)}
+ ))}
+
+ );
+ }
+
+ // TODO: render afterCaret appropriately from props
+ sources = [];
+
+ return (
+
+ {content.map((row) => renderRow(row))}
+ {isOpen && (
+
+ {sources.map(() => (
+
+ ))}
+
+ )}
+ {sources.length > 0 ? renderCaret() : null}
+
+ );
+};
diff --git a/src/components/controls/CardStack.js b/src/components/controls/CardStack.js
index f5faab5..a6d5ad0 100644
--- a/src/components/controls/CardStack.js
+++ b/src/components/controls/CardStack.js
@@ -1,9 +1,6 @@
import React from "react";
import { connect } from "react-redux";
-import {
- generateCardLayout,
- Card,
-} from "@forensic-architecture/design-system/dist/react";
+import { generateCardLayout, Card } from "./Card";
import * as selectors from "../../selectors";
import { getFilterIdxFromColorSet } from "../../common/utilities";
@@ -70,6 +67,7 @@ class CardStack extends React.Component {
return events.map((event, idx) => {
const thisRef = React.createRef();
this.refs[idx] = thisRef;
+ console.log(event);
const content = generateTemplate({
event,
diff --git a/src/components/controls/atoms/Button.js b/src/components/controls/atoms/Button.js
new file mode 100644
index 0000000..12703c1
--- /dev/null
+++ b/src/components/controls/atoms/Button.js
@@ -0,0 +1,86 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+/**
+ * Primary UI component for user interaction
+ */
+export const Button = ({
+ primary,
+ backgroundColor,
+ borderRadius,
+ size,
+ label,
+ normalCursor,
+ ...props
+}) => {
+ const mode = primary ? "button--primary" : "button--secondary";
+ return (
+
+ {label}
+
+ );
+};
+
+Button.propTypes = {
+ /**
+ * Is this the principal call to action on the page?
+ */
+ primary: PropTypes.bool,
+ /**
+ * What background color to use
+ */
+ backgroundColor: PropTypes.string,
+ /**
+ * How much rounded are they?
+ */
+ borderRadius: PropTypes.string,
+ /**
+ * How large should the button be?
+ */
+ size: PropTypes.oneOf(["small", "medium", "large"]),
+ /**
+ * Button contents
+ */
+ label: PropTypes.string.isRequired,
+ /**
+ * Optional click handler
+ */
+ onClick: PropTypes.func,
+};
+
+Button.defaultProps = {
+ backgroundColor: "red",
+ borderRadius: "0%",
+ primary: false,
+ size: "medium",
+ onClick: undefined,
+};
+
+const CardButton = ({
+ text,
+ color = "#000",
+ onClick = () => {},
+ normalCursor,
+}) => (
+
+);
+
+export default CardButton;
diff --git a/src/components/controls/atoms/Caret.js b/src/components/controls/atoms/Caret.js
new file mode 100644
index 0000000..f5931b0
--- /dev/null
+++ b/src/components/controls/atoms/Caret.js
@@ -0,0 +1,15 @@
+import React from "react";
+
+const CardCaret = ({ isOpen, toggle }) => {
+ let classes = isOpen ? "arrow-down" : "arrow-down folded";
+
+ return (
+
+ );
+};
+
+export default CardCaret;
diff --git a/src/components/controls/atoms/CustomField.js b/src/components/controls/atoms/CustomField.js
new file mode 100644
index 0000000..50b847e
--- /dev/null
+++ b/src/components/controls/atoms/CustomField.js
@@ -0,0 +1,12 @@
+import React from "react";
+import marked from "marked";
+
+// TODO could this be a security vulnerability?
+const CardCustomField = ({ title, value }) => (
+
+ {title ?
{title} : null}
+
+
+);
+
+export default CardCustomField;
diff --git a/src/components/controls/atoms/Media.js b/src/components/controls/atoms/Media.js
new file mode 100644
index 0000000..0d8d776
--- /dev/null
+++ b/src/components/controls/atoms/Media.js
@@ -0,0 +1,61 @@
+import React, { useRef } from "react";
+import { useCallback } from "react";
+import { typeForPath } from "../../../common/utilities";
+
+const TITLE_LENGTH = 50;
+// TODO should videos
+// - play inline
+// - appear zoomed out/in
+// - only show cover image and then lightbox when clicked
+// - show video control plane?
+// TODO landscape image doesn't fit in box properly
+const Media = ({ src, title }) => {
+ const videoRef = useRef();
+ const onVideoStart = useCallback(() => {
+ return videoRef.current?.play();
+ }, []);
+ const onVideoStop = useCallback(() => {
+ return videoRef.current?.pause();
+ }, []);
+
+ const type = typeForPath(src);
+ const formattedTitle =
+ title && title.length > TITLE_LENGTH
+ ? `${title.slice(0, TITLE_LENGTH + 1)}...`
+ : title;
+
+ switch (type) {
+ case "Video":
+ return (
+
+ {title &&
{formattedTitle} }
+
+
+
+
+ );
+ case "Image":
+ return (
+
+ {title &&
{formattedTitle} }
+
+
+
+
+ );
+ default:
+ return null;
+ }
+};
+
+export default Media;
diff --git a/src/components/controls/atoms/Text.js b/src/components/controls/atoms/Text.js
new file mode 100644
index 0000000..aedf0cf
--- /dev/null
+++ b/src/components/controls/atoms/Text.js
@@ -0,0 +1,46 @@
+import React, { useState } from "react";
+
+const CardText = ({ title, value, hoverValue = null }) => {
+ const [showHover, setShowHover] = useState(false);
+
+ return (
+
+ {title ?
{title} : null}
+
+
hoverValue && setShowHover(true)}
+ onMouseOut={() => hoverValue && setShowHover(false)}
+ >
+ {showHover ? (
+
+ {hoverValue}
+
+ ) : (
+
+ {value}
+
+ )}
+
+ {/* {!showHover && value} */}
+
+
+ );
+};
+
+export default CardText;
diff --git a/src/components/controls/atoms/Time.js b/src/components/controls/atoms/Time.js
new file mode 100644
index 0000000..59a8224
--- /dev/null
+++ b/src/components/controls/atoms/Time.js
@@ -0,0 +1,29 @@
+import React from "react";
+
+import copy from "../../../common/data/copy.json";
+import { isNotNullNorUndefined } from "../../../common/utilities";
+
+const CardTime = ({ title = "Timestamp", timelabel, language, precision }) => {
+ const unknownLang = copy[language].cardstack.unknown_time;
+
+ if (isNotNullNorUndefined(timelabel)) {
+ return (
+
+ {/* today */}
+
{title}
+ {timelabel}
+ {precision && precision !== "" ? ` - ${precision}` : null}
+
+ );
+ } else {
+ return (
+
+ {/* today */}
+
{title}
+ {unknownLang}
+
+ );
+ }
+};
+
+export default CardTime;
diff --git a/src/components/time/Axis.js b/src/components/time/Axis.js
index 1d2b89a..7390626 100644
--- a/src/components/time/Axis.js
+++ b/src/components/time/Axis.js
@@ -34,14 +34,14 @@ class TimelineAxis extends React.Component {
if (this.props.scaleX) {
this.x0 = d3
.axisBottom(this.props.scaleX)
- .ticks(10)
+ .ticks(5)
.tickPadding(0)
.tickSize(contentHeight - TEXT_HEIGHT - marginTop)
.tickFormat(d3.timeFormat(fstFmt));
this.x1 = d3
.axisBottom(this.props.scaleX)
- .ticks(10)
+ .ticks(5)
.tickPadding(marginTop)
.tickSize(0)
.tickFormat(d3.timeFormat(sndFmt));
diff --git a/src/scss/button.scss b/src/scss/button.scss
new file mode 100644
index 0000000..7fb225b
--- /dev/null
+++ b/src/scss/button.scss
@@ -0,0 +1,37 @@
+.button {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: normal;
+ border: 0;
+ border-radius: 0em;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 1;
+ outline: none;
+ text-align: left;
+}
+.button--primary {
+ color: $offwhite;
+ background-color: $default;
+}
+.button--secondary {
+ color: #333;
+ background-color: transparent;
+ box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 2px inset;
+}
+.button--small {
+ font-size: 12px;
+ padding: 10px 16px;
+ margin: 0.6em 0.3em 0 0;
+}
+.button--medium {
+ font-size: 14px;
+ padding: 11px 20px;
+}
+.button--large {
+ font-size: 16px;
+ padding: 12px 24px;
+}
+
+.no-hover {
+ cursor: auto !important;
+}
diff --git a/src/scss/card.scss b/src/scss/card.scss
index 4005dd0..8ba11a1 100644
--- a/src/scss/card.scss
+++ b/src/scss/card.scss
@@ -1,31 +1,35 @@
.event-card {
box-sizing: border-box;
- margin: 1px 0 0 0;
+ margin: 2px 0;
padding: 15px;
- border: 1px solid $black;
transition: 0.2 ease;
background: $midwhite;
+ opacity: 0.92;
color: $darkgrey;
- box-shadow: 0 19px 19px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
+ list-style-type: none;
font-size: $large;
line-height: $xxlarge;
- height: auto;
- opacity: 0.9;
+ // height: auto;
+ // opacity: 0.9;
transition: background-color 0.4s;
+ text-align: left;
+ overflow-y: scroll;
+ height: 100%;
+ max-width: 400px;
&:hover {
background: $lightwhite;
transition: background-color 0.4s;
- cursor: pointer;
+ // cursor: pointer;
}
h4 {
margin-bottom: 0;
margin-right: 5px;
text-transform: uppercase;
- font-size: $xsmall;
+ font-size: $small;
color: $darkwhite;
- font-weight: 100;
+ font-weight: 800;
&:first-child {
margin-top: 0;
@@ -36,27 +40,23 @@
margin: 0;
}
- .material-icons {
- font-size: $normal;
- color: $darkwhite;
- margin-right: 5px;
+ .card-row,
+ .card-col,
+ .card-cell {
+ margin: 5px 3px 5px 0px;
+ &.m0 {
+ margin: 0;
+ }
}
.card-row,
.card-col {
display: flex;
flex-direction: row;
- margin: 5px 0 10px 0;
- padding-bottom: 10px;
.card-cell {
flex: 1;
}
-
- h4 {
- min-width: 80px;
- max-width: 80px;
- }
}
.card-col {
@@ -72,23 +72,19 @@
display: flex;
flex-direction: row;
align-items: flex-start;
- padding: 8px 15px;
- border-left: 5px solid $darkgrey;
+ padding: 5px 10px;
+ border-left: 3px solid $darkgrey;
background: linear-gradient(to right, $darkgrey 50%, transparent 50%);
background-size: 200% 100%;
background-position: right bottom;
- margin-left: -16px;
- margin-right: -16px;
&:hover {
background-color: $darkgrey;
color: white;
cursor: pointer;
- .material-icons {
- color: white;
- }
+
background-position: left bottom;
- transition: all 2s ease;
+ transition: all 1s ease-in;
}
}
@@ -98,6 +94,13 @@
font-size: 24px;
margin-right: 15px;
}
+
+ .source-type {
+ display: inline-block;
+ margin-right: 5px;
+ text-transform: uppercase;
+ font-weight: bold;
+ }
}
.card-cell {
@@ -177,6 +180,57 @@
}
}
+ .media {
+ display: flex;
+ max-height: 350px;
+ // justify-content: center;
+ flex-direction: column;
+ cursor: pointer;
+
+ .img-wrapper {
+ width: 100%;
+ display: flex;
+ img {
+ // width: auto;
+ // height: 100%;
+
+ max-width: 100%;
+ height: auto;
+ object-fit: cover;
+ // width: 100%;
+ // height: 250px;
+ }
+ }
+
+ video {
+ width: 100%;
+ padding-bottom: 10px;
+ user-select: none;
+ &:focus {
+ outline: 0 !important;
+ }
+ }
+
+ video::-webkit-media-controls-panel {
+ // remove Chrome's gradient
+ background-image: none !important;
+ filter: brightness(0.9);
+ display: flex;
+ align-self: flex-end;
+ // flex-basis: 35px;
+ background-color: rgba($red, 0.6);
+ }
+
+ /* Could Use thise as well for Individual Controls */
+ video::-webkit-media-controls-play-button {
+ align-self: center;
+ }
+
+ video::-webkit-media-controls-timeline {
+ display: none;
+ }
+ }
+
.category {
margin-bottom: 5px;
diff --git a/src/scss/cardstack.scss b/src/scss/cardstack.scss
index 87bbfc8..f850775 100644
--- a/src/scss/cardstack.scss
+++ b/src/scss/cardstack.scss
@@ -1,11 +1,11 @@
// @import 'burger';
-// @import 'card';
+@import "card";
.card-stack {
position: absolute;
top: $card-right;
right: $card-right;
- max-height: calc(100% - 180px);
+ max-height: calc(100% - 260px);
height: auto;
width: $card-width;
overflow-y: scroll;