use comma-api
parent
4bb6453247
commit
839607863b
|
@ -5,7 +5,7 @@
|
|||
* sudo apt-get install -y libusb-dev libudev-dev ruby-sass
|
||||
* yarn install
|
||||
|
||||
## Developemnt
|
||||
## Development
|
||||
|
||||
* yarn run sass
|
||||
* yarn start
|
||||
|
@ -15,7 +15,7 @@
|
|||
* npm version patch
|
||||
* git push # push version patch
|
||||
* yarn build
|
||||
* scripts/deploy.sh
|
||||
* CONTAINER=cabana scripts/deploy.sh
|
||||
|
||||
# Create React App documentation
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
"private": true,
|
||||
"homepage": "https://community.comma.ai/cabana",
|
||||
"dependencies": {
|
||||
"@commaai/comma-api": "^1.0.7",
|
||||
"@commaai/hls.js": "^0.12.2",
|
||||
"@commaai/log_reader": "^0.3.1",
|
||||
"@commaai/my-comma-auth": "^1.0.3",
|
||||
"@commaai/pandajs": "^0.3.4",
|
||||
"ap": "^0.2.0",
|
||||
"aphrodite": "^1.2.1",
|
||||
|
@ -71,7 +73,7 @@
|
|||
"xtend": "^4.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"start": "PORT=3001 react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"build:staging": "env-cmd .env.staging react-app-rewired build",
|
||||
"test": "react-app-rewired test --env=jsdom",
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
CONTAINER=${CONTAINER:-cabana-staging}
|
||||
|
||||
pushd build/
|
||||
find . -not -name "*.map" -type f | while read f; do
|
||||
az storage blob upload --account-name chffrdist --file "$f" --container-name cabana --name "$f"
|
||||
az storage blob upload --account-name chffrdist --file "$f" --container-name $CONTAINER --name "$f"
|
||||
done
|
||||
popd
|
||||
|
||||
pushd public
|
||||
find img -type f | while read f; do
|
||||
az storage blob upload --account-name chffrdist --file "$f" --container-name cabana --name "$f"
|
||||
az storage blob upload --account-name chffrdist --file "$f" --container-name $CONTAINER --name "$f"
|
||||
done
|
||||
popd
|
||||
|
|
|
@ -4,15 +4,14 @@ import PropTypes from "prop-types";
|
|||
import cx from "classnames";
|
||||
import { createWriteStream } from "streamsaver";
|
||||
import Panda from "@commaai/pandajs";
|
||||
|
||||
import CommaAuth from "@commaai/my-comma-auth";
|
||||
import { raw as RawDataApi, drives as DrivesApi } from "@commaai/comma-api";
|
||||
import { USE_UNLOGGER, PART_SEGMENT_LENGTH, STREAMING_WINDOW } from "./config";
|
||||
import * as GithubAuth from "./api/github-auth";
|
||||
|
||||
import * as auth from "./api/comma-auth";
|
||||
import DBC from "./models/can/dbc";
|
||||
import Meta from "./components/Meta";
|
||||
import Explorer from "./components/Explorer";
|
||||
import * as Routes from "./api/routes";
|
||||
import OnboardingModal from "./components/Modals/OnboardingModal";
|
||||
import SaveDbcModal from "./components/SaveDbcModal";
|
||||
import LoadDbcModal from "./components/LoadDbcModal";
|
||||
|
@ -53,7 +52,6 @@ export default class CanExplorer extends Component {
|
|||
messages: {},
|
||||
selectedMessages: [],
|
||||
route: null,
|
||||
routes: [],
|
||||
canFrameOffset: -1,
|
||||
firstCanTime: null,
|
||||
lastBusTime: null,
|
||||
|
@ -123,10 +121,13 @@ export default class CanExplorer extends Component {
|
|||
|
||||
componentWillMount() {
|
||||
const { dongleId, name } = this.props;
|
||||
if (this.props.max && this.props.url) {
|
||||
if (CommaAuth.isAuthenticated() && !name) {
|
||||
// TODO link to explorer
|
||||
this.showOnboarding();
|
||||
} else if (this.props.max && this.props.url) {
|
||||
// probably the demo!
|
||||
const { max, url } = this.props;
|
||||
const { startTime } = Routes.parseRouteName(name);
|
||||
const startTime = Moment(name, "YYYY-MM-DD--H-m-s");
|
||||
|
||||
const route = {
|
||||
fullname: dongleId + "|" + name,
|
||||
|
@ -141,40 +142,35 @@ export default class CanExplorer extends Component {
|
|||
},
|
||||
this.initCanData
|
||||
);
|
||||
} else if (auth.isAuthenticated() && !name) {
|
||||
Routes.fetchRoutes()
|
||||
.then(routes => {
|
||||
const _routes = [];
|
||||
Object.keys(routes).forEach(route => {
|
||||
_routes.push(routes[route]);
|
||||
});
|
||||
this.setState({ routes: _routes });
|
||||
if (!_routes[name]) {
|
||||
this.showOnboarding();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.showOnboarding();
|
||||
});
|
||||
} else if (dongleId && name) {
|
||||
Routes.fetchRoutes(dongleId)
|
||||
.then(routes => {
|
||||
if (routes && routes[name]) {
|
||||
// this makes fullname = dongleId + '|' + name
|
||||
const route = routes[name];
|
||||
const newState = {
|
||||
route,
|
||||
currentParts: [
|
||||
0,
|
||||
Math.min(route.proclog, PART_SEGMENT_LENGTH - 1)
|
||||
]
|
||||
};
|
||||
this.setState(newState, this.initCanData);
|
||||
} else {
|
||||
this.showOnboarding();
|
||||
}
|
||||
const routeName = dongleId + "|" + name;
|
||||
let urlPromise;
|
||||
if (this.props.url) {
|
||||
urlPromise = Promise.resolve(this.props.url);
|
||||
} else {
|
||||
urlPromise = DrivesApi.getRouteInfo(routeName).then(function(route) {
|
||||
return route.url;
|
||||
});
|
||||
}
|
||||
Promise.all([urlPromise, RawDataApi.getLogUrls(routeName)])
|
||||
.then(initData => {
|
||||
let [url, logUrls] = initData;
|
||||
const newState = {
|
||||
route: {
|
||||
fullname: routeName,
|
||||
proclog: logUrls.length - 1,
|
||||
start_time: Moment(name, "YYYY-MM-DD--H-m-s"),
|
||||
url
|
||||
},
|
||||
currentParts: [
|
||||
0,
|
||||
Math.min(logUrls.length - 1, PART_SEGMENT_LENGTH - 1)
|
||||
]
|
||||
};
|
||||
this.setState(newState, this.initCanData);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
this.showOnboarding();
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// API gateway for `api.commadotai.com/v1` urls
|
||||
import { getCommaAccessToken } from "./comma-auth";
|
||||
|
||||
const URL_ROOT = "https://api.commadotai.com/v1/";
|
||||
const ConfigRequest = require("config-request/instance");
|
||||
|
||||
const request = ConfigRequest();
|
||||
|
||||
var initPromise = init();
|
||||
|
||||
async function init() {
|
||||
var token = await getCommaAccessToken();
|
||||
request.configure({
|
||||
baseUrl: URL_ROOT,
|
||||
token: "JWT " + token,
|
||||
jwt: false
|
||||
});
|
||||
}
|
||||
|
||||
export async function get(endpoint, data) {
|
||||
await initPromise;
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get(
|
||||
endpoint,
|
||||
{
|
||||
query: data,
|
||||
json: true
|
||||
},
|
||||
errorHandler(resolve, reject)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function post(endpoint, data) {
|
||||
await initPromise;
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(
|
||||
endpoint,
|
||||
{
|
||||
body: data,
|
||||
json: true
|
||||
},
|
||||
errorHandler(resolve, reject)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function errorHandler(resolve, reject) {
|
||||
return handle;
|
||||
|
||||
function handle(err, data) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import Cookies from "js-cookie";
|
||||
import storage from "localforage";
|
||||
|
||||
import {
|
||||
COMMA_ACCESS_TOKEN_COOKIE,
|
||||
COMMA_OAUTH_REDIRECT_COOKIE
|
||||
} from "../config";
|
||||
|
||||
let isAuthed = false;
|
||||
let useForage = true;
|
||||
|
||||
export async function getCommaAccessToken() {
|
||||
let token = getTokenInternal();
|
||||
if (!token) {
|
||||
try {
|
||||
token = await storage.getItem("authorization");
|
||||
} catch (e) {
|
||||
useForage = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
isAuthed = true;
|
||||
if (useForage) {
|
||||
await storage.setItem("authorization", token);
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
// seed cache
|
||||
getCommaAccessToken();
|
||||
|
||||
function getTokenInternal() {
|
||||
if (typeof localStorage !== "undefined") {
|
||||
if (localStorage.authorization) {
|
||||
return localStorage.authorization;
|
||||
}
|
||||
}
|
||||
return Cookies.get(COMMA_ACCESS_TOKEN_COOKIE);
|
||||
}
|
||||
|
||||
export function isAuthenticated() {
|
||||
return isAuthed;
|
||||
}
|
||||
|
||||
export function authUrl() {
|
||||
Cookies.set(COMMA_OAUTH_REDIRECT_COOKIE, window.location.href);
|
||||
|
||||
return "https://community.comma.ai/forum/ucp.php?mode=login&login=external&oauth_service=google";
|
||||
}
|
|
@ -1,13 +1,27 @@
|
|||
import * as CommaAPI from "./comma-api";
|
||||
import { raw as RawDataApi, request as Request } from "@commaai/comma-api";
|
||||
import CommaAuth from "@commaai/my-comma-auth";
|
||||
import request from "simple-get";
|
||||
|
||||
const urlStore = {};
|
||||
|
||||
var initPromise;
|
||||
function ensureInit() {
|
||||
if (!initPromise) {
|
||||
initPromise = CommaAuth.init().then(function(token) {
|
||||
Request.configure(token);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
return initPromise;
|
||||
}
|
||||
|
||||
export async function getLogURLList(routeName) {
|
||||
if (urlStore[routeName]) {
|
||||
return urlStore[routeName];
|
||||
}
|
||||
var data = await CommaAPI.get("route/" + routeName + "/log_urls");
|
||||
await ensureInit();
|
||||
|
||||
var data = await RawDataApi.getLogUrls(routeName);
|
||||
|
||||
urlStore[routeName] = data;
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import Moment from "moment";
|
||||
import * as CommaAuth from "./comma-auth";
|
||||
|
||||
const ROUTES_ENDPOINT = "https://api.commadotai.com/v1/{dongleId}/routes/";
|
||||
|
||||
function momentizeTimes(routes) {
|
||||
for (let routeName in routes) {
|
||||
routes[routeName].start_time = Moment(routes[routeName].start_time);
|
||||
routes[routeName].end_time = Moment(routes[routeName].end_time);
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
export async function fetchRoutes(dongleId) {
|
||||
// will throw errors from fetch() on HTTP failure
|
||||
|
||||
if (dongleId === undefined) {
|
||||
dongleId = "me";
|
||||
}
|
||||
|
||||
const accessToken = await CommaAuth.getCommaAccessToken();
|
||||
if (accessToken) {
|
||||
const endpoint = ROUTES_ENDPOINT.replace("{dongleId}", dongleId);
|
||||
const headers = new Headers();
|
||||
headers.append("Authorization", `JWT ${accessToken}`);
|
||||
|
||||
const request = new Request(endpoint, { headers });
|
||||
const resp = await fetch(request);
|
||||
const routes = await resp.json();
|
||||
if ("routes" in routes) {
|
||||
return momentizeTimes(routes.routes);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function cameraPath(routeUrl, frame) {
|
||||
return `${routeUrl}/sec${frame}.jpg`;
|
||||
}
|
||||
|
||||
export function parseRouteName(name) {
|
||||
const startTime = Moment(name, "YYYY-MM-DD--H-m-s");
|
||||
return { startTime };
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
const STREAM_VERSION = 2;
|
||||
|
||||
function videoUrl(dongleId, hashedRouteName) {
|
||||
return `${
|
||||
process.env.REACT_APP_VIDEO_CDN
|
||||
}/hls/${dongleId}/${hashedRouteName}/index.m3u8?v=${STREAM_VERSION}`;
|
||||
}
|
||||
|
||||
function videoUrlForRouteUrl(routeUrlString) {
|
||||
const url = new URL(routeUrlString);
|
||||
|
||||
const pathParts = url.pathname.split("/");
|
||||
|
||||
const [dongleIdPrefixed, hashedRouteName] = pathParts.slice(
|
||||
pathParts.length - 2
|
||||
);
|
||||
let dongleId = dongleIdPrefixed;
|
||||
if (dongleIdPrefixed.indexOf("comma-") === 0) {
|
||||
const [, dongleIdNoPrefix] = dongleIdPrefixed.split("comma-");
|
||||
dongleId = dongleIdNoPrefix;
|
||||
}
|
||||
|
||||
return videoUrl(dongleId, hashedRouteName);
|
||||
}
|
||||
|
||||
export default { videoUrl, videoUrlForRouteUrl };
|
|
@ -3,9 +3,9 @@ import PropTypes from "prop-types";
|
|||
import Moment from "moment";
|
||||
import _ from "lodash";
|
||||
import cx from "classnames";
|
||||
import CommaAuth from "@commaai/my-comma-auth";
|
||||
|
||||
import * as auth from "../../api/comma-auth";
|
||||
|
||||
import { EXPLORER_URL } from "../../config";
|
||||
import Modal from "../Modals/baseModal";
|
||||
|
||||
export default class OnboardingModal extends Component {
|
||||
|
@ -25,18 +25,12 @@ export default class OnboardingModal extends Component {
|
|||
this.state = {
|
||||
webUsbEnabled: !!navigator.usb,
|
||||
viewingUsbInstructions: false,
|
||||
pandaConnected: false,
|
||||
chffrDrivesSearch: "",
|
||||
chffrDrivesSortBy: "start_time",
|
||||
chffrDrivesOrderDesc: true
|
||||
pandaConnected: false
|
||||
};
|
||||
|
||||
this.attemptPandaConnection = this.attemptPandaConnection.bind(this);
|
||||
this.toggleUsbInstructions = this.toggleUsbInstructions.bind(this);
|
||||
this.handleSortDrives = this.handleSortDrives.bind(this);
|
||||
this.handleSearchDrives = this.handleSearchDrives.bind(this);
|
||||
this.navigateToAuth = this.navigateToAuth.bind(this);
|
||||
this.openChffrDrive = this.openChffrDrive.bind(this);
|
||||
this.navigateToExplorer = this.navigateToExplorer.bind(this);
|
||||
}
|
||||
|
||||
attemptPandaConnection() {
|
||||
|
@ -52,53 +46,14 @@ export default class OnboardingModal extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
navigateToAuth() {
|
||||
const authUrl = auth.authUrl();
|
||||
window.location.href = authUrl;
|
||||
navigateToExplorer() {
|
||||
window.location.href = EXPLORER_URL;
|
||||
}
|
||||
|
||||
filterRoutesWithCan(drive) {
|
||||
return drive.can === true;
|
||||
}
|
||||
|
||||
handleSearchDrives(drive) {
|
||||
const { chffrDrivesSearch } = this.state;
|
||||
const searchKeywords = chffrDrivesSearch
|
||||
.split(" ")
|
||||
.filter(s => s.length > 0)
|
||||
.map(s => s.toLowerCase());
|
||||
|
||||
return (
|
||||
searchKeywords.length === 0 ||
|
||||
searchKeywords.some(
|
||||
kw =>
|
||||
drive.end_geocode.toLowerCase().indexOf(kw) !== -1 ||
|
||||
drive.start_geocode.toLowerCase().indexOf(kw) !== -1 ||
|
||||
Moment(drive.start_time)
|
||||
.format("dddd MMMM Do YYYY")
|
||||
.toLowerCase()
|
||||
.indexOf(kw) !== -1 ||
|
||||
Moment(drive.end_time)
|
||||
.format("dddd MMMM Do YYYY")
|
||||
.toLowerCase()
|
||||
.indexOf(kw) !== -1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
handleSortDrives(key) {
|
||||
if (this.state.chffrDrivesSortBy === key) {
|
||||
this.setState({ chffrDrivesOrderDesc: !this.state.chffrDrivesOrderDesc });
|
||||
} else {
|
||||
this.setState({ chffrDrivesOrderDesc: true });
|
||||
this.setState({ chffrDrivesSortBy: key });
|
||||
}
|
||||
}
|
||||
|
||||
openChffrDrive(route) {
|
||||
window.location.search = `?route=${route.fullname}`;
|
||||
}
|
||||
|
||||
renderPandaEligibility() {
|
||||
const { webUsbEnabled, pandaConnected } = this.state;
|
||||
const { attemptingPandaConnection } = this.props;
|
||||
|
@ -123,140 +78,27 @@ export default class OnboardingModal extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
renderChffrOption() {
|
||||
const { routes } = this.props;
|
||||
if (routes.length > 0) {
|
||||
return (
|
||||
<div className="cabana-onboarding-mode-chffr">
|
||||
<div className="cabana-onboarding-mode-chffr-search">
|
||||
<div className="form-field--small">
|
||||
<input
|
||||
type="text"
|
||||
id="chffr_drives_search"
|
||||
placeholder="Search chffr drives"
|
||||
value={this.state.chffrDrivesSearch}
|
||||
onChange={e =>
|
||||
this.setState({ chffrDrivesSearch: e.target.value })
|
||||
}
|
||||
/>
|
||||
<div className="cabana-onboarding-mode-chffr-search-helper">
|
||||
<p>(Try: "Drives in San Francisco" or "Drives in June 2017")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-header", {
|
||||
"is-ordered-desc": this.state.chffrDrivesOrderDesc,
|
||||
"is-ordered-asc": !this.state.chffrDrivesOrderDesc
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-date", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "start_time"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("start_time")}
|
||||
>
|
||||
<span>Date</span>
|
||||
</div>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-places", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "end_geocode"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("end_geocode")}
|
||||
>
|
||||
<span>Places</span>
|
||||
</div>
|
||||
<div className={cx("cabana-onboarding-mode-chffr-drive-time")}>
|
||||
<span>Time</span>
|
||||
</div>
|
||||
<div
|
||||
className={cx("cabana-onboarding-mode-chffr-drive-distance", {
|
||||
"is-sorted": this.state.chffrDrivesSortBy === "len"
|
||||
})}
|
||||
onClick={() => this.handleSortDrives("len")}
|
||||
>
|
||||
<span>Distance</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-action" />
|
||||
</div>
|
||||
<ul className="cabana-onboarding-mode-chffr-drives">
|
||||
{_.orderBy(
|
||||
routes,
|
||||
[this.state.chffrDrivesSortBy],
|
||||
[this.state.chffrDrivesOrderDesc ? "desc" : "asc"]
|
||||
)
|
||||
.filter(this.filterRoutesWithCan)
|
||||
.filter(this.handleSearchDrives)
|
||||
.map(route => {
|
||||
const routeDuration = Moment.duration(
|
||||
route.end_time.diff(route.start_time)
|
||||
);
|
||||
const routeStartClock = Moment(route.start_time).format("LT");
|
||||
const routeEndClock = Moment(route.end_time).format("LT");
|
||||
return (
|
||||
<li
|
||||
key={route.fullname}
|
||||
className="cabana-onboarding-mode-chffr-drive"
|
||||
>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-date">
|
||||
<strong>
|
||||
{Moment(route.start_time._i).format("MMM Do")}
|
||||
</strong>
|
||||
<span>{Moment(route.start_time._i).format("dddd")}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-places">
|
||||
<strong>{route.end_geocode}</strong>
|
||||
<span>From {route.start_geocode}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-time">
|
||||
<strong>
|
||||
{routeDuration.hours > 0
|
||||
? `${routeDuration._data.hours} hr `
|
||||
: null}
|
||||
{`${routeDuration._data.minutes} min ${
|
||||
routeDuration._data.seconds
|
||||
} sec`}
|
||||
</strong>
|
||||
<span>{`${routeStartClock} - ${routeEndClock}`}</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-distance">
|
||||
<strong>{route.len.toFixed(2)} mi</strong>
|
||||
<span>{(route.len * 1.6).toFixed(2)} km</span>
|
||||
</div>
|
||||
<div className="cabana-onboarding-mode-chffr-drive-action">
|
||||
<button
|
||||
className="button--primary"
|
||||
onClick={() => this.openChffrDrive(route)}
|
||||
>
|
||||
<span>View Drive</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button
|
||||
onClick={this.navigateToAuth}
|
||||
className="button--primary button--kiosk"
|
||||
>
|
||||
<i className="fa fa-video-camera" />
|
||||
<strong>Log in to View Recorded Drives</strong>
|
||||
<sup>
|
||||
Analyze your car driving data from <em>chffr</em>
|
||||
</sup>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
renderLogin() {
|
||||
return (
|
||||
<button
|
||||
onClick={this.navigateToExplorer}
|
||||
className="button--primary button--kiosk"
|
||||
>
|
||||
<i className="fa fa-video-camera" />
|
||||
<strong>
|
||||
{CommaAuth.isAuthenticated()
|
||||
? "Find a drive in Explorer"
|
||||
: "Log in with Explorer"}
|
||||
</strong>
|
||||
<sup>Click "View CAN Data" while replaying a drive</sup>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
renderOnboardingOptions() {
|
||||
return (
|
||||
<div className="cabana-onboarding-modes">
|
||||
<div className="cabana-onboarding-mode">{this.renderChffrOption()}</div>
|
||||
<div className="cabana-onboarding-mode">{this.renderLogin()}</div>
|
||||
<div className="cabana-onboarding-mode">
|
||||
<button
|
||||
className={cx("button--secondary button--kiosk", {
|
||||
|
@ -371,8 +213,8 @@ export default class OnboardingModal extends Component {
|
|||
render() {
|
||||
return (
|
||||
<Modal
|
||||
title="Welcome to cabana"
|
||||
subtitle="Get started by viewing your chffr drives or enabling live mode"
|
||||
title="Welcome to Cabana"
|
||||
subtitle="Get started by selecting a drive from Explorer or enabling live mode"
|
||||
footer={this.renderModalFooter()}
|
||||
disableClose={true}
|
||||
variations={["wide", "dark"]}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { StyleSheet, css } from "aphrodite/no-important";
|
||||
import { derived as RouteApi, video as VideoApi } from "@commaai/comma-api";
|
||||
|
||||
import HLS from "./HLS";
|
||||
import { cameraPath } from "../api/routes";
|
||||
import Video from "../api/video";
|
||||
import RouteSeeker from "./RouteSeeker/RouteSeeker";
|
||||
|
||||
const Styles = StyleSheet.create({
|
||||
|
@ -93,7 +92,7 @@ export default class RouteVideoSync extends Component {
|
|||
nearestFrameUrl() {
|
||||
const { url } = this.props;
|
||||
const sec = Math.round(this.props.userSeekTime);
|
||||
return cameraPath(url, sec);
|
||||
return RouteApi(url).getJpegUrl(sec);
|
||||
}
|
||||
|
||||
loadingOverlay() {
|
||||
|
@ -170,7 +169,7 @@ export default class RouteVideoSync extends Component {
|
|||
) : null}
|
||||
<HLS
|
||||
className={css(Styles.hls)}
|
||||
source={Video.videoUrlForRouteUrl(this.props.url)}
|
||||
source={VideoApi(this.props.url).getRearCameraStreamIndexUrl()}
|
||||
startTime={this.props.userSeekTime}
|
||||
playbackSpeed={this.props.playSpeed}
|
||||
onVideoElementAvailable={this.onVideoElementAvailable}
|
||||
|
|
|
@ -28,5 +28,8 @@ export const CAN_GRAPH_MAX_POINTS = 10000;
|
|||
|
||||
export const STREAMING_WINDOW = 60;
|
||||
|
||||
export const COMMA_ACCESS_TOKEN_COOKIE = "comma_access_token";
|
||||
export const COMMA_OAUTH_REDIRECT_COOKIE = "wiki_login_redirect";
|
||||
const ENV_EXPLORER_URL = {
|
||||
debug: "http://127.0.0.1:3000/",
|
||||
prod: "https://my.comma.ai/"
|
||||
};
|
||||
export const EXPLORER_URL = ENV_EXPLORER_URL[ENV];
|
||||
|
|
20
src/index.js
20
src/index.js
|
@ -1,6 +1,8 @@
|
|||
import Sentry from "./logging/Sentry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import CommaAuth from "@commaai/my-comma-auth";
|
||||
import { request as Request } from "@commaai/comma-api";
|
||||
import CanExplorer from "./CanExplorer";
|
||||
import AcuraDbc from "./acura-dbc";
|
||||
import { getUrlParameter, modifyQueryParameters } from "./utils/url";
|
||||
|
@ -28,11 +30,13 @@ if (routeFullName) {
|
|||
|
||||
let max = getUrlParameter("max"),
|
||||
url = getUrlParameter("url");
|
||||
if (max && url) {
|
||||
if (max) {
|
||||
props.max = max;
|
||||
props.url = url;
|
||||
props.isShare = true;
|
||||
}
|
||||
if (url) {
|
||||
props.url = url;
|
||||
}
|
||||
props.isShare = max && url;
|
||||
} else if (getUrlParameter("demo")) {
|
||||
props.max = 12;
|
||||
props.url =
|
||||
|
@ -78,8 +82,16 @@ if (authTokenQueryParam !== null) {
|
|||
props.githubAuthToken = fetchPersistedGithubAuthToken();
|
||||
}
|
||||
|
||||
if (routeFullName || isDemo) {
|
||||
async function init() {
|
||||
const token = await CommaAuth.init();
|
||||
if (token) {
|
||||
Request.configure(token);
|
||||
}
|
||||
ReactDOM.render(<CanExplorer {...props} />, document.getElementById("root"));
|
||||
}
|
||||
|
||||
if (routeFullName || isDemo) {
|
||||
init();
|
||||
} else {
|
||||
const img = document.createElement("img");
|
||||
img.src = process.env.PUBLIC_URL + "/img/cabana.jpg";
|
||||
|
|
65
yarn.lock
65
yarn.lock
|
@ -17,6 +17,16 @@
|
|||
lodash "^4.2.0"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@commaai/comma-api@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@commaai/comma-api/-/comma-api-1.0.9.tgz#f5627c47392107c600cbb4f419ad65b89a6ccf35"
|
||||
integrity sha512-NJPL2dRK9c+ipfOlS3mr9+DFZSUyYAxcbHZysagH5ChPKHXtSbggQKS/KValLA7bxdXVBhNalh21ufmqoQIejg==
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
config-request "^0.5.1"
|
||||
joi-browser "^13.4.0"
|
||||
querystringify "^2.1.1"
|
||||
|
||||
"@commaai/hls.js@^0.12.2":
|
||||
version "0.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@commaai/hls.js/-/hls.js-0.12.2.tgz#694433f0de5e454a1b116daf83f9f6d4b6f285ab"
|
||||
|
@ -39,6 +49,18 @@
|
|||
geval "^2.2.0"
|
||||
stream-selector "^0.4.0"
|
||||
|
||||
"@commaai/my-comma-auth@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@commaai/my-comma-auth/-/my-comma-auth-1.0.3.tgz#030e95aa2ef8775824e453f67519544ef61f6bf0"
|
||||
integrity sha512-S4cSV0l5WV4vUQ3N+3wRS2oWX7Bk9PnoXU3sV9IDTGIotsjxeJOw9SRsknqYEGnOc3UwPg73OIyQAyH3qmuWvA==
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
comma-api "https://github.com/commaai/comma-api.git#full-spec"
|
||||
config-request "^0.5.1"
|
||||
global "^4.4.0"
|
||||
localforage "^1.7.3"
|
||||
querystringify "^2.1.1"
|
||||
|
||||
"@commaai/pandajs@^0.3.4":
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@commaai/pandajs/-/pandajs-0.3.4.tgz#b8ad7f1e1cee2cce15f14f38cbf6f1ba8162814b"
|
||||
|
@ -1252,6 +1274,7 @@ babel-register@^6.26.0:
|
|||
babel-runtime@6.26.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
|
||||
dependencies:
|
||||
core-js "^2.4.0"
|
||||
regenerator-runtime "^0.11.0"
|
||||
|
@ -1976,6 +1999,15 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
"comma-api@https://github.com/commaai/comma-api.git#full-spec":
|
||||
version "1.0.7"
|
||||
resolved "https://github.com/commaai/comma-api.git#6dc346876c16aa4ac48884c1261b2bb29720cbb0"
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
config-request "^0.5.1"
|
||||
joi-browser "^13.4.0"
|
||||
querystringify "^2.1.1"
|
||||
|
||||
commander@2, commander@2.15.x, commander@^2.11.0, commander@^2.15.1, commander@^2.9.0, commander@~2.15.0:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
|
@ -2115,7 +2147,12 @@ core-js@^1.0.0:
|
|||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0:
|
||||
core-js@^2.4.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||
|
||||
core-js@^2.4.1, core-js@^2.5.0:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.5.tgz#b14dde936c640c0579a6b50cabcc132dd6127e3b"
|
||||
|
||||
|
@ -3919,6 +3956,14 @@ global@^4.3.2, global@~4.3.0:
|
|||
min-document "^2.19.0"
|
||||
process "~0.5.1"
|
||||
|
||||
global@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
|
||||
dependencies:
|
||||
min-document "^2.19.0"
|
||||
process "^0.11.10"
|
||||
|
||||
globals@^9.17.0, globals@^9.18.0:
|
||||
version "9.18.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
|
||||
|
@ -5161,6 +5206,11 @@ jest@20.0.4:
|
|||
dependencies:
|
||||
jest-cli "^20.0.4"
|
||||
|
||||
joi-browser@^13.4.0:
|
||||
version "13.4.0"
|
||||
resolved "https://registry.yarnpkg.com/joi-browser/-/joi-browser-13.4.0.tgz#b72ba61b610e3f58e51b563a14e0f5225cfb6896"
|
||||
integrity sha512-TfzJd2JaJ/lg/gU+q5j9rLAjnfUNF9DUmXTP9w+GfmG79LjFOXFeM7hIFuXCBcZCivUDFwd9l1btTV9rhHumtQ==
|
||||
|
||||
js-base64@^2.1.8, js-base64@^2.1.9:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
|
||||
|
@ -5500,6 +5550,13 @@ localforage@^1.7.1:
|
|||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
localforage@^1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204"
|
||||
integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==
|
||||
dependencies:
|
||||
lie "3.1.1"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
|
@ -7107,6 +7164,11 @@ querystringify@^2.0.0:
|
|||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755"
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
|
||||
raf@3.4.0, raf@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
|
||||
|
@ -7423,6 +7485,7 @@ regenerate@^1.2.1:
|
|||
regenerator-runtime@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-transform@^0.10.0:
|
||||
version "0.10.1"
|
||||
|
|
Loading…
Reference in New Issue