diff --git a/buildCore.js b/buildCore.js
index ae86616..28a4b70 100644
--- a/buildCore.js
+++ b/buildCore.js
@@ -42,7 +42,8 @@ if (process.argv.some((v) => v === "--nightly")) {
// but it forgets to pass the LTO flag that we specified in the
// Cargo.toml, so the virtual-function-elimination complains that it's
// only compatible with LTO, so we have to specify lto here too.
- rustFlags += " -Z virtual-function-elimination -C lto";
+ // FIXME: Seems to be broken at the moment.
+ // rustFlags += " -Z virtual-function-elimination -C lto";
}
}
@@ -55,7 +56,7 @@ execSync(
);
execSync(
- `cargo ${toolchain} rustc -p livesplit-core-capi --crate-type cdylib --features wasm-web --target wasm32-unknown-unknown ${cargoFlags}`,
+ `cargo ${toolchain} rustc -p livesplit-core-capi --crate-type cdylib --features wasm-web,web-rendering --target wasm32-unknown-unknown ${cargoFlags}`,
{
cwd: "livesplit-core",
stdio: "inherit",
diff --git a/livesplit-core b/livesplit-core
index f957c93..a7c9df2 160000
--- a/livesplit-core
+++ b/livesplit-core
@@ -1 +1 @@
-Subproject commit f957c93b03aafcca925fcf35a2f4949f7b921f58
+Subproject commit a7c9df2ef1da927e743bd4a0783a5266d70ac402
diff --git a/src/assets/icon.svg b/src/assets/icon.svg
index 1cdb312..8c93ee3 100644
--- a/src/assets/icon.svg
+++ b/src/assets/icon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/css/Abbreviated.scss b/src/css/Abbreviated.scss
deleted file mode 100644
index 1864e35..0000000
--- a/src/css/Abbreviated.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.abbreviated {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- width: 100%;
-}
diff --git a/src/css/DetailedTimer.scss b/src/css/DetailedTimer.scss
deleted file mode 100644
index 19f433e..0000000
--- a/src/css/DetailedTimer.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-@import 'Time';
-@import 'variables';
-
-.detailed-timer {
- position: relative;
-
- @include time;
-
- .detailed-timer-left-side {
- position: absolute;
- display: flex;
- top: 0;
- left: $side-padding;
- height: 100%;
- width: 50%;
-
- .detailed-timer-icon-container {
- height: 100%;
- padding: $vertical-padding $side-padding;
- margin-right: $side-padding;
- flex-shrink: 0;
- box-sizing: border-box;
-
- .detailed-timer-icon-inner-container {
- width: 100%;
- height: 100%;
- text-align: center;
- vertical-align: center;
-
- .detailed-timer-icon {
- max-width: 100%;
- height: 100%;
- object-fit: contain;
- }
- }
- }
-
- .detailed-timer-left-side-text {
- position: relative;
- width: 100%;
-
- .detailed-timer-segment-name {
- position: absolute;
- top: $vertical-padding;
- left: 0;
- width: 100%;
- text-overflow: ellipsis;
- overflow: hidden;
- white-space: nowrap;
- }
-
- .detailed-timer-comparisons {
- position: absolute;
- left: 0;
- bottom: $vertical-padding;
- }
- }
- }
-}
diff --git a/src/css/Graph.scss b/src/css/Graph.scss
deleted file mode 100644
index fb499f1..0000000
--- a/src/css/Graph.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.graph {
- display: block;
-}
diff --git a/src/css/KeyValue.scss b/src/css/KeyValue.scss
deleted file mode 100644
index 61c3d81..0000000
--- a/src/css/KeyValue.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-@import 'Time';
-@import 'variables';
-
-.key-value {
- height: $default-component-height;
- padding: 0 $side-padding;
- display: flex;
-
- @include time;
-
- &.key-value-two-rows {
- height: $two-row-height;
- padding-top: $vertical-padding;
- padding-bottom: $vertical-padding;
- flex-wrap: wrap;
- align-items: stretch;
-
- .key-value-value {
- width: 100%;
- }
- }
-
- &.key-value-center {
- justify-content: center;
- text-align: center;
-
- .key-value-key {
- justify-content: center;
- }
- }
-
- &.key-value-left-right {
- .key-value-key {
- padding-right: $side-padding;
- }
- }
-
- .key-value-key {
- display: flex;
- flex-grow: 1;
- justify-content: flex-start;
- align-items: center;
- overflow: hidden;
- }
-
- .key-value-value {
- display: flex;
- flex-shrink: 0;
- justify-content: flex-end;
- align-items: center;
- overflow: hidden;
-
- .key-value-value-inner {
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- }
-}
diff --git a/src/css/Layout.scss b/src/css/Layout.scss
index 7e63595..3604991 100644
--- a/src/css/Layout.scss
+++ b/src/css/Layout.scss
@@ -1,27 +1,42 @@
-@import 'variables';
@import 'Font';
.layout {
- font-family: "fira", sans-serif;
- font-size: $default-font-size;
- text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
- position: relative;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- user-select: none;
-
.resizable-layout {
position: absolute;
- z-index: 2;
- height: 100% !important;
+ top: 0;
+ width: inherit;
+ height: inherit;
- .resizable-handle {
+ .react-resizable {
+ width: 0 !important;
+ height: 0 !important;
+ }
+
+ .resizable-handle-east {
cursor: e-resize;
- bottom: -4px;
- right: -4px;
+ right: -10px;
+ bottom: 10px;
+ top: 0;
position: absolute;
width: 20px;
- height: 100%;
+ }
+
+ .resizable-handle-south {
+ cursor: s-resize;
+ bottom: -10px;
+ left: 0;
+ right: 10px;
+ position: absolute;
+ height: 20px;
+ }
+
+ .resizable-handle-south-east {
+ cursor: se-resize;
+ bottom: -10px;
+ right: -10px;
+ position: absolute;
+ width: 20px;
+ height: 20px;
}
}
}
diff --git a/src/css/Separator.scss b/src/css/Separator.scss
deleted file mode 100644
index 1b5dbdf..0000000
--- a/src/css/Separator.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-@import 'variables';
-
-.separator {
- background-color: hsla(0, 0%, 43%, 1);
- height: $thick-separator-width;
-}
diff --git a/src/css/Splits.scss b/src/css/Splits.scss
deleted file mode 100644
index f223a4a..0000000
--- a/src/css/Splits.scss
+++ /dev/null
@@ -1,160 +0,0 @@
-@use 'sass:math';
-
-@import "Time";
-@import 'variables';
-
-$split-column-width: $default-line-height * 3;
-$split-label-height: $default-component-height - $thick-separator-width;
-$split-icon-size: $default-component-height - $vertical-padding * 2;
-$split-icon-two-row-size: $two-row-height - $vertical-padding * 2;
-
-.splits {
- width: 100%;
- position: relative;
-
- .split {
- display: flex;
- position: relative;
- width: 100%;
- height: $default-component-height;
-
- @include time;
-
- &.two-rows {
- height: $two-row-height;
-
- .split-rows {
- flex-wrap: wrap;
- padding-top: $vertical-padding;
- padding-bottom: $vertical-padding;
-
- .split-row {
- height: initial;
-
- &.split-second-row {
- width: 100%;
- justify-content: flex-end;
- }
- }
- }
-
- .split-icon-container {
- width: $split-icon-two-row-size;
- }
- }
-
- .split-icon-container {
- padding: $vertical-padding $side-padding;
- width: $split-icon-size;
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- position: relative;
-
- .split-icon {
- width: 100%;
- height: 100%;
- object-fit: contain;
- filter: drop-shadow(2px 2px 1px rgba(0, 0, 0, 0.3));
- }
- }
-
- .split-icon-container-empty {
- padding-right: $side-padding;
- position: relative;
- }
-
- .split-rows {
- display: flex;
- align-items: center;
- flex-grow: 1;
- overflow: hidden;
- height: 100%;
- position: relative;
- box-sizing: border-box;
-
- .split-row {
- display: flex;
- align-items: center;
- overflow: hidden;
- position: relative;
- height: 100%;
-
- &.split-first-row,
- .split-name {
- flex-grow: 1;
- }
-
- &.split-second-row,
- .split-time {
- flex-shrink: 0;
- }
-
- .split-name {
- display: flex;
- align-items: center;
- justify-content: flex-start;
- overflow: hidden;
- padding: 0 $side-padding;
- padding-left: 0;
- height: 100%;
-
- .split-name-inner {
- padding: 0;
- max-width: 100%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- }
-
- .split-time {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- padding: 0 $side-padding;
- padding-left: 0;
- height: 100%;
-
- &.split-time-full {
- width: $split-column-width;
- flex-shrink: 0;
- }
-
- .split-time-inner {
- padding: 0;
- }
- }
- }
- }
-
- &.split-label {
- height: $split-label-height;
- }
-
- .current-split-background,
- .split-borders {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- .split-borders {
- border-bottom-width: $thin-separator-width;
- border-top-width: $thin-separator-width;
- border-bottom-style: solid;
- border-top-style: solid;
- top: math.div(-$thick-separator-width, 2);
- height: calc(100% + #{math.div($thick-separator-width, 2)});
-
- &.separator-above {
- border-top-width: $thick-separator-width;
- border-top-style: solid;
- }
- }
- }
-}
diff --git a/src/css/Time.scss b/src/css/Time.scss
deleted file mode 100644
index ae56428..0000000
--- a/src/css/Time.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@mixin time {
- .time {
- font-variant-numeric: tabular-nums;
- }
-}
diff --git a/src/css/Timer.scss b/src/css/Timer.scss
deleted file mode 100644
index 52203ea..0000000
--- a/src/css/Timer.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.timer {
- will-change: transform;
- transform: translateZ(0);
-}
diff --git a/src/css/Title.scss b/src/css/Title.scss
deleted file mode 100644
index 40eefd2..0000000
--- a/src/css/Title.scss
+++ /dev/null
@@ -1,99 +0,0 @@
-@import "variables";
-
-$game-icon-size: $two-row-height - $vertical-padding * 2;
-
-.title {
- height: $two-row-height;
- width: 100%;
- display: flex;
- padding-top: $vertical-padding;
- padding-bottom: $vertical-padding;
-
- .game-icon-container {
- max-width: $game-icon-size;
- max-height: $game-icon-size;
- padding: $vertical-padding 0;
- padding-left: $side-padding;
- flex-shrink: 0;
-
- .title-icon {
- width: 100%;
- height: 100%;
- object-fit: contain;
- }
- }
-
- .title-text {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .run-info {
- height: 100%;
- flex-grow: 1;
- overflow: hidden;
-
- .title-game {
- display: flex;
- align-items: center;
- height: 50%;
- padding: 0 $side-padding;
-
- &.centered .abbreviated {
- text-align: center;
- }
- }
-
- .lower-row {
- display: flex;
- position: relative;
- height: 50%;
-
- &.centered {
- justify-content: center;
- }
-
- &.one-line {
- height: 100%;
- }
-
- .title-category {
- display: flex;
- align-items: center;
- height: 100%;
- flex-grow: 1;
- overflow: hidden;
- padding: 0 $side-padding;
-
- &.centered .abbreviated {
- text-align: center;
- }
- }
-
- .title-attempts,
- .title-attempts-invisible {
- height: 100%;
- flex-shrink: 0;
- display: flex;
- align-items: center;
-
- &.one-line {
- height: 50%;
- align-self: flex-end;
- }
- }
-
- .title-attempts {
- justify-content: flex-end;
- padding-right: $side-padding;
- }
-
- .title-attempts-invisible {
- justify-content: flex-start;
- padding-left: $side-padding;
- visibility: hidden;
- }
- }
- }
-}
diff --git a/src/index.tsx b/src/index.tsx
index 747a4d6..f0d6a52 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -25,6 +25,7 @@ try {
layout,
hotkeys,
layoutWidth,
+ layoutHeight,
} = await LiveSplit.loadStoredData();
function requestWakeLock() {
@@ -43,6 +44,10 @@ try {
}
});
+ // The renderer requires the fonts to be loaded before it gets created
+ // as otherwise information may be cached incorrectly.
+ await document.fonts.ready;
+
ReactDOM.render(
{
- public sorted: string[];
- public oldAbbreviations: string[];
- public element: React.RefObject;
-
- constructor(props: Props) {
- super(props);
- this.element = React.createRef();
- this.sorted = [...this.props.abbreviations];
- this.sorted.sort((a, b) => b.length - a.length);
- this.oldAbbreviations = this.props.abbreviations;
- }
-
- public componentDidMount() {
- const element = expect(this.element.current, "Element should exist by now.");
- new (window as any).ResizeObserver(() => {
- this.chooseAbbreviation();
- }).observe(element);
-
- this.chooseAbbreviation();
- }
-
- public componentDidUpdate() {
- const arrEq = (a: string[], b: string[]) => {
- if (a.length !== b.length) {
- return false;
- }
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
- };
- if (!arrEq(this.props.abbreviations, this.oldAbbreviations)) {
- this.oldAbbreviations = this.props.abbreviations;
- this.sorted = [...this.props.abbreviations];
- this.sorted.sort((a, b) => b.length - a.length);
- this.chooseAbbreviation();
- }
- }
-
- public render() {
- return ;
- }
-
- private chooseAbbreviation() {
- if (this.element.current === null) {
- // We can still get here for a short time when unmounting.
- return;
- }
-
- for (const abbrev of this.sorted) {
- this.element.current.innerText = abbrev;
- if (this.element.current.scrollWidth <= this.element.current.clientWidth) {
- return;
- }
- }
- this.element.current.innerText = this.sorted[0];
- }
-}
diff --git a/src/layout/AutoRefreshLayout.tsx b/src/layout/AutoRefreshLayout.tsx
deleted file mode 100644
index 0a6574a..0000000
--- a/src/layout/AutoRefreshLayout.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import * as React from "react";
-import { LayoutStateJson } from "../livesplit-core";
-import AutoRefresh from "../util/AutoRefresh";
-import Layout from "./Layout";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- getState: () => LayoutStateJson,
- layoutUrlCache: UrlCache,
- allowResize: boolean,
- width: number,
- onResize(width: number): void,
-}
-
-export interface State {
- layoutState: LayoutStateJson,
-}
-
-export default class AutoRefreshLayout extends React.Component {
- constructor(props: Props) {
- super(props);
-
- this.state = {
- layoutState: this.props.getState(),
- };
- }
-
- public refreshLayout() {
- this.setState({
- layoutState: this.props.getState(),
- });
- }
-
- public render() {
- return (
- this.refreshLayout()} >
-
-
- );
- }
-}
diff --git a/src/layout/BlankSpace.tsx b/src/layout/BlankSpace.tsx
deleted file mode 100644
index d47c9e5..0000000
--- a/src/layout/BlankSpace.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import { gradientToCss } from "../util/ColorUtil";
-
-export interface Props { state: LiveSplit.BlankSpaceComponentStateJson }
-
-export default class BlankSpace extends React.Component {
- public render() {
- return (
-
- );
- }
-}
diff --git a/src/layout/Component.tsx b/src/layout/Component.tsx
deleted file mode 100644
index 0345153..0000000
--- a/src/layout/Component.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from "react";
-import deepEqual from "fast-deep-equal";
-import { ComponentStateJson } from "../livesplit-core";
-import { assertNever } from "../util/OptionUtil";
-import BlankSpace from "./BlankSpace";
-import KeyValue from "./KeyValue";
-import DetailedTimer from "./DetailedTimer";
-import Graph from "./Graph";
-import Separator from "./Separator";
-import Splits from "./Splits";
-import Text from "./Text";
-import Timer from "./Timer";
-import Title from "./Title";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- state: ComponentStateJson,
- layoutUrlCache: UrlCache,
- layoutWidth: number,
- componentId: string,
-}
-
-export default class Component extends React.Component {
- public render() {
- const { state } = this.props;
- if ("KeyValue" in state) {
- return ;
- } else if ("DetailedTimer" in state) {
- return ;
- } else if ("Graph" in state) {
- return ;
- } else if ("Splits" in state) {
- return ;
- } else if ("Text" in state) {
- return ;
- } else if ("Timer" in state) {
- return ;
- } else if ("Title" in state) {
- return ;
- } else if ("Separator" in state) {
- return ;
- } else if ("BlankSpace" in state) {
- return ;
- } else {
- return assertNever(state);
- }
- }
-
- public shouldComponentUpdate(nextProps: Readonly): boolean {
- return !deepEqual(nextProps.state, this.props.state) ||
- nextProps.layoutWidth !== this.props.layoutWidth ||
- nextProps.componentId !== this.props.componentId;
- }
-}
diff --git a/src/layout/DetailedTimer.tsx b/src/layout/DetailedTimer.tsx
deleted file mode 100644
index f45302e..0000000
--- a/src/layout/DetailedTimer.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import { colorToCss, gradientToCss } from "../util/ColorUtil";
-import { renderToSVG } from "./Timer";
-
-import "../css/DetailedTimer.scss";
-import variables from "../css/variables.scss";
-import { map } from "../util/OptionUtil";
-import { UrlCache } from "../util/UrlCache";
-
-const fontSizeToLineHeightRatio = parseFloat(variables.fontSizeToLineHeightRatio);
-const lineHeightToComponentHeightRatio = parseFloat(variables.lineHeightToComponentHeightRatio);
-const sidePadding = parseFloat(variables.sidePadding);
-
-export interface Props {
- state: LiveSplit.DetailedTimerComponentStateJson,
- layoutUrlCache: UrlCache,
- layoutWidth: number,
-}
-
-export default class DetailedTimer extends React.Component {
- constructor(props: Props) {
- super(props);
- }
-
- public render() {
- const icon = this.props.layoutUrlCache.cache(this.props.state.icon);
-
- const totalHeight = this.props.state.timer.height + this.props.state.segment_timer.height;
- const topRowHeight = totalHeight * 0.55;
- const bottomRowHeight = totalHeight - topRowHeight;
- const topRowFontSize = topRowHeight * 0.5 * fontSizeToLineHeightRatio;
- const bottomRowFontSize = bottomRowHeight * 0.5 * lineHeightToComponentHeightRatio * fontSizeToLineHeightRatio;
-
- const comparisonNamesColor = map(this.props.state.comparison_names_color, colorToCss);
- const comparisonTimesColor = map(this.props.state.comparison_times_color, colorToCss);
-
- return (
-
- {
- renderToSVG(
- this.props.state.timer,
- "timer",
- this.props.layoutWidth,
- )
- }
- {
- renderToSVG(
- this.props.state.segment_timer,
- "segment-timer",
- this.props.layoutWidth,
- )
- }
-
- {
- icon !== undefined &&
-
-
-

-
-
- }
-
- {
- this.props.state.segment_name !== null &&
-
- {this.props.state.segment_name}
-
- }
-
-
- {
- this.props.state.comparison1 !== null &&
- formatComparison(
- this.props.state.comparison1,
- comparisonNamesColor,
- comparisonTimesColor,
- )
- }
- {
- this.props.state.comparison2 !== null &&
- formatComparison(
- this.props.state.comparison2,
- comparisonNamesColor,
- comparisonTimesColor,
- )
- }
-
-
-
-
-
- );
- }
-}
-
-function formatComparison(
- comparison: LiveSplit.DetailedTimerComponentComparisonStateJson,
- comparisonNamesColor?: string,
- comparisonTimesColor?: string,
-) {
- return (
-
- |
- {comparison.name + ":"}
- |
-
- {comparison.time}
- |
-
- );
-}
diff --git a/src/layout/DragAutoRefreshLayout.tsx b/src/layout/DragAutoRefreshLayout.tsx
deleted file mode 100644
index 5ed6179..0000000
--- a/src/layout/DragAutoRefreshLayout.tsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as React from "react";
-import { LayoutStateJson } from "../livesplit-core";
-import AutoRefresh from "../util/AutoRefresh";
-import { backgroundToCss, colorToCss } from "../util/ColorUtil";
-import { Option } from "../util/OptionUtil";
-import Component from "./Component";
-import { getLayoutStateStyle } from "./Layout";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- getState: () => LayoutStateJson,
- layoutUrlCache: UrlCache,
- layoutWidth: number,
- onClick: (componentIndex: number) => void,
- onDrag: (componentIndex: number) => void,
- onDragEnd: (componentIndex: number) => void,
- onDrop: (componentIndex: number) => void,
- isSelected: (componentIndex: number) => boolean,
-}
-
-export interface State {
- layoutState: LayoutStateJson,
- startIndex: Option,
- hoverIndex: Option,
-}
-
-export default class DragAutoRefreshLayout extends React.Component {
- constructor(props: Props) {
- super(props);
-
- this.state = {
- hoverIndex: null,
- layoutState: this.props.getState(),
- startIndex: null,
- };
- }
-
- public refreshLayout() {
- this.setState({
- layoutState: this.props.getState(),
- });
- }
-
- public render() {
- const layoutState = this.state.layoutState;
- const layoutStateStyle = getLayoutStateStyle(layoutState);
- const counts = new Map();
-
- const background = backgroundToCss(
- layoutState.background,
- this.props.layoutUrlCache,
- this.props.layoutWidth,
- );
-
- const dragLayout = (
-
-
- {
- layoutState.components.map((c, i) => {
- const componentType = Object.keys(c)[0];
- const id = counts.get(componentType) || 0;
- counts.set(componentType, id + 1);
-
- const key = `${componentType}${id}`;
-
- return
this.props.onClick(i)}
- draggable
- onDragStart={(e) => {
- e.dataTransfer.setData("text/plain", "");
- this.setState({
- ...this.state,
- hoverIndex: i,
- startIndex: i,
- });
- this.props.onDrag(i);
- }}
- onDragOver={(e) => {
- if (e.preventDefault) {
- e.preventDefault();
- }
- e.dataTransfer.dropEffect = "move";
- }}
- onDragEnter={(_) => {
- this.setState({
- ...this.state,
- hoverIndex: i,
- });
- }}
- onDragEnd={(_) => {
- this.props.onDragEnd(i);
- this.setState({
- ...this.state,
- hoverIndex: null,
- startIndex: null,
- });
- }}
- onDrop={(e) => {
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- this.props.onDrop(i);
- return false;
- }}
- style={{
- cursor: "pointer",
- position: "relative",
- }}
- >
- {
- getBorderDiv(i, this.state, this.props)
- }
-
-
;
- })
- }
-
- );
-
- return (
- this.refreshLayout()}>
- {dragLayout}
-
- );
- }
-}
-
-function getBorderDiv(index: number, state: State, props: Props): Option {
- const style: any = {
- height: "100%",
- position: "absolute",
- width: "100%",
- zIndex: 2,
- };
-
- if (state.startIndex == null || state.startIndex === state.hoverIndex) {
- if (props.isSelected(index)) {
- style.border = "2px solid rgb(50, 114, 241)";
- style.width = "calc(100% - 4px)";
- style.height = "calc(100% - 4px)";
- } else {
- return null;
- }
- } else if (index === state.hoverIndex) {
- if (index < state.startIndex) {
- style.borderTop = "2px solid rgb(50, 114, 241)";
- } else {
- style.borderBottom = "2px solid rgb(50, 114, 241)";
- style.height = "calc(100% - 2px)";
- }
- } else {
- return null;
- }
-
- return ;
-}
diff --git a/src/layout/Graph.tsx b/src/layout/Graph.tsx
deleted file mode 100644
index d3d8d51..0000000
--- a/src/layout/Graph.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import { colorToCss } from "../util/ColorUtil";
-
-import "../css/Graph.scss";
-
-export interface Props {
- state: LiveSplit.GraphComponentStateJson,
- layoutWidth: number,
-}
-
-export default class Graph extends React.Component {
- public render() {
- const width = this.props.layoutWidth;
- const height = this.props.state.height;
-
- const middle = height * this.props.state.middle;
-
- const colorTop = colorToCss(this.props.state.top_background_color);
- const colorBottom = colorToCss(this.props.state.bottom_background_color);
- const colorGridLines = colorToCss(this.props.state.grid_lines_color);
- const colorGraphLines = colorToCss(this.props.state.graph_lines_color);
- const colorPartialFill = colorToCss(this.props.state.partial_fill_color);
- const colorCompleteFill = colorToCss(this.props.state.complete_fill_color);
- const colorBestSegment = colorToCss(this.props.state.best_segment_color);
-
- const rect1 = ;
- const rect2 = ;
- const children = [rect1, rect2];
-
- this.props.state.horizontal_grid_lines.forEach((y, i) => {
- const line = ;
- children.push(line);
- });
-
- this.props.state.vertical_grid_lines.forEach((x, i) => {
- const line = ;
- children.push(line);
- });
-
- let points = "";
-
- let length = this.props.state.points.length;
- if (this.props.state.is_live_delta_active) {
- length -= 1;
- }
-
- for (let i = 0; i < length; i++) {
- const point = this.props.state.points[i];
- points += `${width * point.x},${height * point.y} `;
- }
-
- points += `${width * this.props.state.points[length - 1].x},${middle}`;
-
- const fill = ;
-
- children.push(fill);
-
- if (this.props.state.is_live_delta_active) {
- const x1 = width * this.props.state.points[length - 1].x;
- const y1 = height * this.props.state.points[length - 1].y;
- const x2 = width * this.props.state.points[length].x;
- const y2 = height * this.props.state.points[length].y;
-
- const fill = ;
-
- children.push(fill);
- }
-
- const childrenPoints = [];
-
- for (let i = 1; i < this.props.state.points.length; i++) {
- const px = `${100 * this.props.state.points[i - 1].x}%`;
- const py = height * this.props.state.points[i - 1].y;
- const x = `${100 * this.props.state.points[i].x}%`;
- const y = height * this.props.state.points[i].y;
-
- const fill = this.props.state.points[i].is_best_segment
- ? colorBestSegment
- : colorGraphLines;
-
- const line = ;
- children.push(line);
-
- if (i !== this.props.state.points.length - 1 || !this.props.state.is_live_delta_active) {
- const circle = ;
- childrenPoints.push(circle);
- }
- }
-
- children.push(...childrenPoints);
-
- return ();
- }
-}
diff --git a/src/layout/KeyValue.tsx b/src/layout/KeyValue.tsx
deleted file mode 100644
index 1733823..0000000
--- a/src/layout/KeyValue.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import KeyValueGeneric, { KeyValueDisplay } from "./KeyValueGeneric";
-
-import "../css/KeyValue.scss";
-
-export interface Props {
- state: LiveSplit.KeyValueComponentStateJson,
-}
-
-export default class KeyValue extends React.Component {
- public render() {
- return ;
- }
-}
diff --git a/src/layout/KeyValueGeneric.tsx b/src/layout/KeyValueGeneric.tsx
deleted file mode 100644
index 0af8adb..0000000
--- a/src/layout/KeyValueGeneric.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-
-import { colorToCss, gradientToCss } from "../util/ColorUtil";
-import { map } from "../util/OptionUtil";
-import Abbreviated from "./Abbreviated";
-
-import "../css/KeyValue.scss";
-
-export enum KeyValueDisplay {
- Center,
- SplitOneRow,
- SplitTwoRows,
-}
-
-export interface Props {
- display: KeyValueDisplay,
- keyColor: LiveSplit.Color | null,
- keyText: string,
- keyAbbreviations: string[],
- valueColor: LiveSplit.Color | null,
- valueText: string | null,
- wrapperBackground: LiveSplit.Gradient,
-}
-
-export default class KeyValueGeneric extends React.Component {
- public render() {
- const keyCell = ;
-
- const valueCell =
-
{this.props.valueText}
-
;
-
- let keyValueRows: JSX.Element;
- let wrapperClassName: string;
- if (this.props.display === KeyValueDisplay.Center) {
- keyValueRows = keyCell;
- wrapperClassName = "key-value-center";
- } else {
- keyValueRows = <>
- {keyCell}
- {valueCell}
- >;
-
- if (this.props.display === KeyValueDisplay.SplitTwoRows) {
- wrapperClassName = "key-value-two-rows";
- } else {
- wrapperClassName = "key-value-left-right";
- }
- }
-
- return (
-
- {keyValueRows}
-
- );
- }
-}
diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx
index 28e7f92..3f4faa2 100644
--- a/src/layout/Layout.tsx
+++ b/src/layout/Layout.tsx
@@ -1,145 +1,68 @@
import * as React from "react";
-import { Font, FontStretch, FontStyle, FontWeight, LayoutStateJson } from "../livesplit-core";
-import { backgroundToCss, colorToCss } from "../util/ColorUtil";
-import Component from "./Component";
-import { ResizableBox, ResizeCallbackData } from "react-resizable";
-
-import "../css/Layout.scss";
+import { ResizableBox } from "react-resizable";
+import { LayoutStateRef } from "../livesplit-core";
+import { WebRenderer } from "../livesplit-core/livesplit_core";
+import AutoRefresh from "../util/AutoRefresh";
import { UrlCache } from "../util/UrlCache";
-
-interface LayoutStateStyle {
- "--thin-separators-color": string,
- "--separators-color": string,
-
- "--timer-font-family"?: string,
- "--timer-font-style"?: FontStyle,
- "--timer-font-weight"?: string | number,
- "--timer-font-stretch"?: FontStretch,
-
- "--times-font-family"?: string,
- "--times-font-style"?: FontStyle,
- "--times-font-weight"?: string | number,
- "--times-font-stretch"?: FontStretch,
-
- "--text-font-family"?: string,
- "--text-font-style"?: FontStyle,
- "--text-font-weight"?: string | number,
- "--text-font-stretch"?: FontStretch,
-}
+import "../css/Layout.scss";
export interface Props {
- state: LayoutStateJson,
+ getState: () => LayoutStateRef,
layoutUrlCache: UrlCache,
allowResize: boolean,
width: number,
- onResize(width: number): void,
+ height: number,
+ renderer: WebRenderer,
+ onResize(width: number, height: number): void,
}
-export default class Layout extends React.Component {
+export default class Layout extends React.Component {
+ public refreshLayout() {
+ const layoutState = this.props.getState();
+ const newDims = this.props.renderer.render(layoutState.ptr, this.props.layoutUrlCache.imageCache.ptr);
+ if (newDims !== undefined) {
+ this.props.onResize(newDims[0], newDims[1]);
+ }
+ }
+
public render() {
- const layoutState = getLayoutStateStyle(this.props.state);
- const counts = new Map();
-
- const background = backgroundToCss(
- this.props.state.background,
- this.props.layoutUrlCache,
- this.props.width,
- );
-
return (
-
-
- {this.props.allowResize &&
-
{ event.stopPropagation(); }} className="resizable-handle" />}
- onResize={
- (
- _event: React.SyntheticEvent,
- data: ResizeCallbackData,
- ) => this.props.onResize(data.size.width)
- }
- axis="x"
+ this.refreshLayout()} >
+
+
element?.appendChild(this.props.renderer.element())}
/>
- }
- {
- this.props.state.components.map((c) => {
- const componentType = Object.keys(c)[0];
- const id = counts.get(componentType) || 0;
- counts.set(componentType, id + 1);
-
- const key = `${componentType}${id}`;
-
- return ;
- })
- }
-
+ {
+ this.props.allowResize &&
+ e.stopPropagation()} className="resizable-handle-east" />}
+ onResize={(_event, data) => this.props.onResize(data.size.width, data.size.height)}
+ />
+ e.stopPropagation()} className="resizable-handle-south" />}
+ onResize={(_event, data) => this.props.onResize(data.size.width, data.size.height)}
+ />
+ e.stopPropagation()} className="resizable-handle-south-east" />}
+ onResize={(_event, data) => this.props.onResize(data.size.width, data.size.height)}
+ />
+
+ }
+
+
);
}
}
-
-export function getLayoutStateStyle(state: LayoutStateJson): LayoutStateStyle {
- const style = {
- "--thin-separators-color": colorToCss(state.thin_separators_color),
- "--separators-color": colorToCss(state.separators_color),
- "--times-font-weight": "bold",
- };
-
- fillFont(style, "timer", state.timer_font, "timer, sans-serif");
- fillFont(style, "times", state.times_font, "fira, sans-serif");
- fillFont(style, "text", state.text_font, "fira, sans-serif");
-
- return style;
-}
-
-function fillFont(style: any, prefix: string, font: Font | null, defaultName: string) {
- if (font == null) {
- style[`--${prefix}-font-family`] = defaultName;
- return;
- }
- style[`--${prefix}-font-family`] = `"${font.family}", ${defaultName}`;
- style[`--${prefix}-font-style`] = font.style;
- style[`--${prefix}-font-weight`] = translateWeight(font.weight);
- style[`--${prefix}-font-stretch`] = font.stretch;
-}
-
-function translateWeight(weight: FontWeight): string | number {
- switch (weight) {
- case "bold":
- case "normal": return weight;
- case "thin": return 100;
- case "extra-light": return 200;
- case "light": return 300;
- case "semi-light": return 350;
- case "normal": return 400;
- case "medium": return 500;
- case "semi-bold": return 600;
- case "bold": return 700;
- case "extra-bold": return 800;
- case "black": return 900;
- case "extra-black": return 950;
- }
-}
diff --git a/src/layout/Separator.tsx b/src/layout/Separator.tsx
deleted file mode 100644
index beb0ff5..0000000
--- a/src/layout/Separator.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as React from "react";
-
-import "../css/Separator.scss";
-
-export default class Separator extends React.Component {
- public render() {
- return (
-
- );
- }
-}
diff --git a/src/layout/Split.tsx b/src/layout/Split.tsx
deleted file mode 100644
index dd3a68b..0000000
--- a/src/layout/Split.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import * as React from "react";
-import deepEqual from "fast-deep-equal";
-import * as LiveSplit from "../livesplit-core";
-import { colorToCss, gradientToCss } from "../util/ColorUtil";
-import { Option } from "../util/OptionUtil";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- splitsState: {
- has_icons: boolean,
- show_thin_separators: boolean,
- display_two_rows: boolean,
- current_split_gradient: LiveSplit.Gradient,
- },
- evenOdd: [Option, Option],
- split: LiveSplit.SplitStateJson,
- separatorInFrontOfSplit: boolean,
- visualSplitIndex: number,
- layoutUrlCache: UrlCache,
-}
-
-export default class Split extends React.Component {
- constructor(props: Props) {
- super(props);
- }
-
- public render() {
- const currentSplit = this.props.split.is_current_split ? "current-split" : "";
- const twoRows = this.props.splitsState.display_two_rows ? "two-rows" : "";
-
- const splitsHaveIcons = this.props.splitsState.has_icons;
- const icon = splitsHaveIcons
- ? this.props.layoutUrlCache.cache(this.props.split.icon)
- : undefined;
-
- const innerStyle: any = {};
- const outerStyle: any = {};
-
- if (this.props.split.index % 2 === 1) {
- if (this.props.splitsState.show_thin_separators) {
- innerStyle.borderBottomColor = "var(--thin-separators-color)";
- } else {
- innerStyle.borderBottomColor = "transparent";
- }
- outerStyle.backgroundColor = this.props.evenOdd[1];
- } else {
- innerStyle.borderBottomColor = "transparent";
- outerStyle.backgroundColor = this.props.evenOdd[0];
- }
- innerStyle.borderTopColor = innerStyle.borderBottomColor;
-
- let separatorAbove = "";
- if (this.props.separatorInFrontOfSplit) {
- innerStyle.borderTopColor = "var(--separators-color)";
- separatorAbove = "separator-above";
- }
-
- const currentSplitBackgroundStyle: any = {};
- if (this.props.split.is_current_split) {
- currentSplitBackgroundStyle.background = gradientToCss(this.props.splitsState.current_split_gradient);
- }
-
- let lastEmptyColumnIndex = this.props.split.columns.length;
- for (let i = this.props.split.columns.length - 1; i >= 0; i--) {
- const column = this.props.split.columns[i];
- if (!column.value) {
- lastEmptyColumnIndex = i;
- } else {
- break;
- }
- }
-
- const columns = this.props.split.columns.slice(0, lastEmptyColumnIndex);
-
- return (
- s.length > 0).join(" ")}
- style={outerStyle}
- >
-
-
-
- {
- splitsHaveIcons && icon !== undefined &&
-

- }
-
-
-
-
-
- {this.props.split.name}
-
-
-
-
- {
- columns.map((column, i) =>
-
,
- ).reverse()
- }
-
-
-
- );
- }
-
- public shouldComponentUpdate(nextProps: Readonly): boolean {
- return !deepEqual(nextProps.splitsState, this.props.splitsState) ||
- !deepEqual(nextProps.evenOdd, this.props.evenOdd) ||
- !deepEqual(nextProps.split, this.props.split) ||
- nextProps.separatorInFrontOfSplit !== this.props.separatorInFrontOfSplit ||
- nextProps.visualSplitIndex !== this.props.visualSplitIndex;
- }
-}
diff --git a/src/layout/SplitLabels.tsx b/src/layout/SplitLabels.tsx
deleted file mode 100644
index a2232eb..0000000
--- a/src/layout/SplitLabels.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as React from "react";
-
-export interface Props {
- labels: string[],
-}
-
-export default class SplitLabels extends React.Component {
- public render() {
- return (
-
-
-
-
-
-
- {
- this.props.labels.map((label, i) =>
-
- {label}
-
,
- ).reverse()
- }
-
-
-
- );
- }
-}
diff --git a/src/layout/Splits.tsx b/src/layout/Splits.tsx
deleted file mode 100644
index 51b9a7a..0000000
--- a/src/layout/Splits.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import Split from "./Split";
-import { gradientToCss, colorToCss } from "../util/ColorUtil";
-import { Option, map } from "../util/OptionUtil";
-import SplitLabels from "./SplitLabels";
-
-import "../css/Splits.scss";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- state: LiveSplit.SplitsComponentStateJson,
- layoutUrlCache: UrlCache,
-}
-
-export default class Splits extends React.Component {
- constructor(props: Props) {
- super(props);
- }
-
- public render() {
- const background = this.props.state.background;
- const style: any = {};
- let evenOdd: [Option, Option] = [null, null];
- if ("Alternating" in background) {
- evenOdd = [
- colorToCss(background.Alternating[0]),
- colorToCss(background.Alternating[1]),
- ];
- } else {
- style.background = gradientToCss(background.Same);
- }
-
- const splitsState = {
- has_icons: this.props.state.has_icons,
- show_thin_separators: this.props.state.show_thin_separators,
- display_two_rows: this.props.state.display_two_rows,
- current_split_gradient: this.props.state.current_split_gradient
- };
-
- return (
-
- {
- map(
- this.props.state.column_labels,
- (labels) => ,
- )
- }
- {
- this.props.state.splits.map((s, i) =>
- ,
- )
- }
-
- );
- }
-}
diff --git a/src/layout/Text.tsx b/src/layout/Text.tsx
deleted file mode 100644
index 5b7f715..0000000
--- a/src/layout/Text.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import KeyValueGeneric, { KeyValueDisplay } from "./KeyValueGeneric";
-
-import "../css/KeyValue.scss";
-
-export interface Props {
- state: LiveSplit.TextComponentStateJson,
-}
-
-export default class Text extends React.Component {
- constructor(props: Props) {
- super(props);
- }
-
- public render() {
- const { text } = this.props.state;
-
- if ("Center" in text) {
- return ;
- }
-
- return ;
- }
-}
diff --git a/src/layout/Timer.tsx b/src/layout/Timer.tsx
deleted file mode 100644
index 25b8c41..0000000
--- a/src/layout/Timer.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import { colorToCss, gradientToCss } from "../util/ColorUtil";
-
-import variables from "../css/variables.scss";
-import "../css/Timer.scss";
-
-const sidePadding = parseFloat(variables.sidePadding);
-
-export interface Props {
- state: LiveSplit.TimerComponentStateJson,
- componentId: string,
- layoutWidth: number,
-}
-
-export function renderToSVG(
- state: LiveSplit.TimerComponentStateJson,
- className: string,
- componentWidth: number,
-): JSX.Element {
- const time = state.time;
- const fraction = state.fraction;
- const height = state.height;
-
- const y = `${0.82 * height}px`;
-
- const alignState: { x: string, element: SVGTextElement | null } = {
- x: "",
- element: null,
- };
-
- const updateAlign = () => {
- alignState.element?.setAttribute("x", alignState.x);
- };
-
- return (
-
-
-
- );
-}
-
-export default class Timer extends React.Component {
- public render() {
- return renderToSVG(
- this.props.state,
- this.props.componentId,
- this.props.layoutWidth,
- );
- }
-}
diff --git a/src/layout/Title.tsx b/src/layout/Title.tsx
deleted file mode 100644
index 8820f78..0000000
--- a/src/layout/Title.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as React from "react";
-import * as LiveSplit from "../livesplit-core";
-import { colorToCss, gradientToCss } from "../util/ColorUtil";
-import { map } from "../util/OptionUtil";
-
-import "../css/Title.scss";
-import Abbreviated from "./Abbreviated";
-import { UrlCache } from "../util/UrlCache";
-
-export interface Props {
- state: LiveSplit.TitleComponentStateJson,
- layoutUrlCache: UrlCache,
-}
-
-export default class Title extends React.Component {
- constructor(props: Props) {
- super(props);
- }
-
- public render() {
- const iconUrl = this.props.layoutUrlCache.cache(this.props.state.icon);
-
- const finishedRunsExist = this.props.state.finished_runs !== null;
- const attemptsExist = this.props.state.attempts !== null;
- const line2 = this.props.state.line2;
- const twoLines = line2.length !== 0;
- const showIcon = iconUrl !== undefined;
- const showAttempts = attemptsExist || finishedRunsExist;
-
- let attemptsLabel = "";
- if (finishedRunsExist) {
- attemptsLabel += this.props.state.finished_runs;
- if (attemptsExist) {
- attemptsLabel += "/";
- }
- }
- if (attemptsExist) {
- attemptsLabel += this.props.state.attempts;
- }
-
- const alignmentClass = this.props.state.is_centered ? "centered" : "left-align";
- const numLinesClass = twoLines ? "two-lines" : "one-line";
- const iconClass = showIcon ? "show-icon" : "";
-
- return (
-
- {
- showIcon &&
-

-
- }
-
- {
- twoLines &&
-
-
- }
-
- {
- this.props.state.is_centered && showAttempts &&
-
- }
-
- {
- showAttempts &&
-
- }
-
-
-
- );
- }
-}
diff --git a/src/storage/index.tsx b/src/storage/index.tsx
index a135f3d..eeec0a7 100644
--- a/src/storage/index.tsx
+++ b/src/storage/index.tsx
@@ -6,6 +6,7 @@ export type HotkeyConfigSettings = unknown;
export type LayoutSettings = unknown;
const DEFAULT_LAYOUT_WIDTH = 300;
+const DEFAULT_LAYOUT_HEIGHT = 500;
let db: Option> = null;
@@ -206,16 +207,20 @@ export async function loadHotkeys(): Promise {
return await db.get("settings", "hotkeys");
}
-export async function storeLayoutWidth(layoutWidth: number) {
+export async function storeLayoutDims(layoutWidth: number, layoutHeight: number) {
const db = await getDb();
await db.put("settings", layoutWidth, "layoutWidth");
+ await db.put("settings", layoutHeight, "layoutHeight");
}
-export async function loadLayoutWidth(): Promise {
+export async function loadLayoutDims(): Promise<[number, number]> {
const db = await getDb();
- return await db.get("settings", "layoutWidth") ?? DEFAULT_LAYOUT_WIDTH;
+ return [
+ await db.get("settings", "layoutWidth") ?? DEFAULT_LAYOUT_WIDTH,
+ await db.get("settings", "layoutHeight") ?? DEFAULT_LAYOUT_HEIGHT,
+ ];
}
export async function storeSplitsKey(splitsKey?: number) {
diff --git a/src/ui/LayoutEditor.tsx b/src/ui/LayoutEditor.tsx
index c124fb4..6533aa3 100644
--- a/src/ui/LayoutEditor.tsx
+++ b/src/ui/LayoutEditor.tsx
@@ -1,18 +1,22 @@
import * as React from "react";
import { ContextMenu, ContextMenuTrigger, MenuItem } from "react-contextmenu";
-import DragAutoRefreshLayout from "../layout/DragAutoRefreshLayout";
import * as LiveSplit from "../livesplit-core";
import { SettingsComponent } from "./Settings";
+import { UrlCache } from "../util/UrlCache";
+import Layout from "../layout/Layout";
+import { WebRenderer } from "../livesplit-core/livesplit_core";
import "../css/LayoutEditor.scss";
-import { UrlCache } from "../util/UrlCache";
export interface Props {
editor: LiveSplit.LayoutEditor,
+ layoutState: LiveSplit.LayoutStateRefMut,
layoutEditorUrlCache: UrlCache,
layoutUrlCache: UrlCache,
layoutWidth: number,
+ layoutHeight: number,
timer: LiveSplit.SharedTimerRef,
+ renderer: WebRenderer,
callbacks: Callbacks,
}
export interface State {
@@ -21,6 +25,7 @@ export interface State {
}
interface Callbacks {
+ onResize(width: number, height: number): Promise,
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
closeLayoutEditor(save: boolean): void,
}
@@ -254,24 +259,20 @@ export class LayoutEditor extends React.Component {
- this.props.timer.readWith(
(t) => {
- const result = this.props.editor.layoutStateAsJson(this.props.layoutUrlCache.imageCache, t);
+ this.props.editor.updateLayoutState(this.props.layoutState, this.props.layoutUrlCache.imageCache, t);
this.props.layoutUrlCache.collect();
- return result;
- }
+ return this.props.layoutState;
+ },
)}
layoutUrlCache={this.props.layoutUrlCache}
- layoutWidth={this.props.layoutWidth}
- onClick={(i) => this.selectComponent(i)}
- onDrag={(i) => {
- this.props.editor.select(i);
- this.update();
- }}
- onDragEnd={(_) => this.update()}
- onDrop={(i) => this.props.editor.moveComponent(i)}
- isSelected={(i) => this.state.editor.selected_component === i}
+ allowResize={true}
+ width={this.props.layoutWidth}
+ height={this.props.layoutHeight}
+ renderer={this.props.renderer}
+ onResize={(width, height) => this.props.callbacks.onResize(width, height)}
/>
diff --git a/src/ui/LayoutView.tsx b/src/ui/LayoutView.tsx
index 19ecfd8..59fc7b2 100644
--- a/src/ui/LayoutView.tsx
+++ b/src/ui/LayoutView.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
import { SharedTimer, Layout, LayoutStateRefMut } from "../livesplit-core";
import { TimerView } from "./TimerView";
import { UrlCache } from "../util/UrlCache";
+import { WebRenderer } from "../livesplit-core/livesplit_core";
export interface Props {
isDesktop: boolean,
@@ -9,9 +10,11 @@ export interface Props {
layoutState: LayoutStateRefMut,
layoutUrlCache: UrlCache,
layoutWidth: number,
+ layoutHeight: number,
renderWithSidebar: boolean,
sidebarOpen: boolean,
timer: SharedTimer,
+ renderer: WebRenderer,
callbacks: Callbacks,
}
@@ -21,7 +24,7 @@ interface Callbacks {
importLayoutFromFile(file: File): Promise,
importSplitsFromFile(file: File): Promise,
loadDefaultLayout(): void,
- onResize(width: number): Promise,
+ onResize(width: number, height: number): Promise,
openAboutView(): void,
openLayoutEditor(): void,
openLayoutView(): void,
@@ -39,10 +42,12 @@ export class LayoutView extends React.Component {
layoutState={this.props.layoutState}
layoutUrlCache={this.props.layoutUrlCache}
layoutWidth={this.props.layoutWidth}
+ layoutHeight={this.props.layoutHeight}
isDesktop={this.props.isDesktop}
renderWithSidebar={false}
sidebarOpen={this.props.sidebarOpen}
timer={this.props.timer}
+ renderer={this.props.renderer}
callbacks={this.props.callbacks}
/>;
const sidebarContent = this.renderSidebarContent();
diff --git a/src/ui/LiveSplit.tsx b/src/ui/LiveSplit.tsx
index 632ccb2..10de1cb 100644
--- a/src/ui/LiveSplit.tsx
+++ b/src/ui/LiveSplit.tsx
@@ -17,10 +17,11 @@ import { SplitsSelection, EditingInfo } from "./SplitsSelection";
import { LayoutView } from "./LayoutView";
import { toast } from "react-toastify";
import * as Storage from "../storage";
+import { UrlCache } from "../util/UrlCache";
+import { WebRenderer } from "../livesplit-core/livesplit_core";
import "react-toastify/dist/ReactToastify.css";
import "../css/LiveSplit.scss";
-import { UrlCache } from "../util/UrlCache";
export enum MenuKind {
Timer,
@@ -46,6 +47,7 @@ export interface Props {
layout?: Storage.LayoutSettings,
hotkeys?: Storage.HotkeyConfigSettings,
layoutWidth: number,
+ layoutHeight: number,
splitsKey?: number,
}
@@ -59,12 +61,15 @@ export interface State {
runEditorUrlCache: UrlCache,
layoutEditorUrlCache: UrlCache,
layoutWidth: number,
+ layoutHeight: number,
menu: Menu,
openedSplitsKey?: number,
sidebarOpen: boolean,
sidebarTransitionsEnabled: boolean,
storedLayoutWidth: number,
+ storedLayoutHeight: number,
timer: SharedTimer,
+ renderer: WebRenderer,
}
export let hotkeySystem: Option = null;
@@ -75,7 +80,7 @@ export class LiveSplit extends React.Component {
const splits = splitsKey !== undefined ? await Storage.loadSplits(splitsKey) : undefined;
const layout = await Storage.loadLayout();
const hotkeys = await Storage.loadHotkeys();
- const layoutWidth = await Storage.loadLayoutWidth();
+ const [layoutWidth, layoutHeight] = await Storage.loadLayoutDims();
return {
splits,
@@ -83,6 +88,7 @@ export class LiveSplit extends React.Component {
layout,
hotkeys,
layoutWidth,
+ layoutHeight,
};
}
@@ -149,6 +155,9 @@ export class LiveSplit extends React.Component {
const isDesktop = this.isDesktopQuery.matches;
const isBrowserSource = !!(window as any).obsstudio;
+ const renderer = WebRenderer.new();
+ renderer.element().setAttribute("style", "width: inherit; height: inherit;");
+
this.state = {
isDesktop: isDesktop && !isBrowserSource,
isBrowserSource,
@@ -158,13 +167,16 @@ export class LiveSplit extends React.Component {
runEditorUrlCache: new UrlCache(),
layoutEditorUrlCache: new UrlCache(),
layoutWidth: props.layoutWidth,
+ layoutHeight: props.layoutHeight,
menu: { kind: MenuKind.Timer },
sidebarOpen: false,
sidebarTransitionsEnabled: false,
storedLayoutWidth: props.layoutWidth,
+ storedLayoutHeight: props.layoutHeight,
timer,
hotkeySystem,
openedSplitsKey: props.splitsKey,
+ renderer,
};
this.mediaQueryChanged = this.mediaQueryChanged.bind(this);
@@ -237,10 +249,13 @@ export class LiveSplit extends React.Component {
} else if (this.state.menu.kind === MenuKind.LayoutEditor) {
return ;
} else if (this.state.menu.kind === MenuKind.SettingsEditor) {
@@ -263,10 +278,12 @@ export class LiveSplit extends React.Component {
layoutState={this.state.layoutState}
layoutUrlCache={this.state.layoutUrlCache}
layoutWidth={this.state.layoutWidth}
+ layoutHeight={this.state.layoutHeight}
isDesktop={this.state.isDesktop}
renderWithSidebar={true}
sidebarOpen={this.state.sidebarOpen}
timer={this.state.timer}
+ renderer={this.state.renderer}
callbacks={this}
/>;
} else if (this.state.menu.kind === MenuKind.Layout) {
@@ -275,10 +292,12 @@ export class LiveSplit extends React.Component {
layoutState={this.state.layoutState}
layoutUrlCache={this.state.layoutUrlCache}
layoutWidth={this.state.layoutWidth}
+ layoutHeight={this.state.layoutHeight}
isDesktop={this.state.isDesktop}
renderWithSidebar={true}
sidebarOpen={this.state.sidebarOpen}
timer={this.state.timer}
+ renderer={this.state.renderer}
callbacks={this}
/>;
}
@@ -492,12 +511,14 @@ export class LiveSplit extends React.Component {
this.openTimerView();
}
- public async onResize(width: number) {
+ public async onResize(width: number, height: number) {
this.setState({
layoutWidth: width,
+ layoutHeight: height,
storedLayoutWidth: width,
+ storedLayoutHeight: height,
});
- await Storage.storeLayoutWidth(width);
+ await Storage.storeLayoutDims(width, height);
}
private getDefaultRun() {
diff --git a/src/ui/TimerView.tsx b/src/ui/TimerView.tsx
index 93958ff..0ab0564 100644
--- a/src/ui/TimerView.tsx
+++ b/src/ui/TimerView.tsx
@@ -1,28 +1,32 @@
import * as React from "react";
import { toast } from "react-toastify";
import {
- SharedTimer, TimerRef, TimerRefMut, Layout,
+ SharedTimer, TimerRef, TimerRefMut,
TimingMethod, TimeSpan, LayoutStateRefMut,
} from "../livesplit-core";
+import * as LiveSplit from "../livesplit-core";
import { Option } from "../util/OptionUtil";
import DragUpload from "./DragUpload";
import AutoRefresh from "../util/AutoRefresh";
-import AutoRefreshLayout from "../layout/AutoRefreshLayout";
+import Layout from "../layout/Layout";
+import { UrlCache } from "../util/UrlCache";
+import { WebRenderer } from "../livesplit-core/livesplit_core";
import LiveSplitIcon from "../assets/icon.svg";
import "../css/TimerView.scss";
-import { UrlCache } from "../util/UrlCache";
export interface Props {
isDesktop: boolean,
- layout: Layout,
+ layout: LiveSplit.Layout,
layoutState: LayoutStateRefMut,
layoutUrlCache: UrlCache,
layoutWidth: number,
+ layoutHeight: number,
renderWithSidebar: boolean,
sidebarOpen: boolean,
timer: SharedTimer,
+ renderer: WebRenderer,
callbacks: Callbacks,
}
export interface State {
@@ -33,7 +37,7 @@ export interface State {
interface Callbacks {
importLayoutFromFile(file: File): Promise,
importSplitsFromFile(file: File): Promise,
- onResize(width: number): Promise,
+ onResize(width: number, height: number): Promise,
openAboutView(): void,
openLayoutView(): void,
openSplitsView(): void,
@@ -80,36 +84,38 @@ export class TimerView extends React.Component {
cursor: "pointer",
}}
>
- this.readWith(
(t) => {
- const result = this.props.layout.updateStateAsJson(this.props.layoutState, this.props.layoutUrlCache.imageCache, t);
+ this.props.layout.updateState(this.props.layoutState, this.props.layoutUrlCache.imageCache, t);
this.props.layoutUrlCache.collect();
- return result;
+ return this.props.layoutState;
},
)}
layoutUrlCache={this.props.layoutUrlCache}
allowResize={this.props.isDesktop}
width={this.props.layoutWidth}
- onResize={(width) => this.props.callbacks.onResize(width)}
+ height={this.props.layoutHeight}
+ renderer={this.props.renderer}
+ onResize={(width, height) => this.props.callbacks.onResize(width, height)}
/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
;
diff --git a/test/rendering-test.js b/test/rendering-test.js
index 783f5ae..cbb136c 100644
--- a/test/rendering-test.js
+++ b/test/rendering-test.js
@@ -101,6 +101,10 @@ describe("Layout Rendering Tests", function () {
await clickElement(By.xpath(".//button[contains(text(), 'Back')]"));
const layoutElement = await findElement(By.className("layout"));
+
+ // The icons load asynchronously, so we need to wait for them to load.
+ await driver.sleep(500);
+
const layoutScreenshot = await layoutElement.takeScreenshot();
const tempFilePath = `${SCREENSHOTS_FOLDER}/${layoutName}.png`;
@@ -149,15 +153,15 @@ describe("Layout Rendering Tests", function () {
});
}
- testRendering("all_components", "default", "f4G-QgAAAAAeAAAO____________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA__wCAAAAAABv______4PZ4AG");
- testRendering("all_components", "pmw3", "f4G-QgAAAAAeAAAO________fmAOf6AAX4AG3_gH3_AOX_APVkgOX-AOVvAPX_AeX_AeWrAeb_gfAAAAAABv__x-____JABO");
- testRendering("default", "default", "____________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAD_________");
- testRendering("default", "pmw3", "__wA___GX-AG3-gGUNgGQkAOXnAOW9gOX-APVtwOH_APX8AOX_APRnAOXVAOXJAeHvAeX_wf__AfAAAAAABgAAD_Pz9_____");
- testRendering("splits_two_rows", "celeste", "f8AAcAAHf8AA____d4AAcAAHf_gAcAAPd_AAYAAHf4AA____f4AAYAAH__AA____Z_gAYAAHf_8A9VVfb_wAcAAPf_AAYAAP");
- testRendering("splits_with_labels", "celeste", "AABEAADPAADPAADPAADA____fAAH_wAP_wAP_wAPxgAA____wgAH34AP_4AH34APQYAA____0EAP_8AP38AP38APQAAAAAAA");
- testRendering("title_centered_no_game_icon", "celeste", "________AAAAADQAAHQAAH4A______3_____AAAAAAAAAAAA____AAkAAL8DAP8DAP8DAP8D_________-n9AAAAAAAAAAAA");
- testRendering("title_centered_with_game_icon", "celeste", "________AAAAQBoAQBoAYB8A________9V-U4AAA4AAA4AAA4AAA4ASA4H_D4H_D4H_D4H_D_________9T84AAAAAAAAAAA");
- testRendering("title_left_no_attempt_count", "celeste", "________AAAA-AAA-AAA-AAA____________AAAAAAAAAAAA____rVKV_wAA_4AA_4AA_4AA________qf__AAAAAAAAAAAA");
+ testRendering("all_components", "default", "PwD-XAAAAAA2AAAO____________________AAAAAAAAAAAA____AAAA____AAAA____AAAAf_AAd_AAAAD-Nz___34HbwAG");
+ testRendering("all_components", "pmw3", "PwD-XAAAAAA2AAAO_________sAPf8AAf4AC3_AH_-AOX-APXfAGX-AP18AOf-APXeAOX-AOD7AOQIAIAAD-b_j_____LwBs");
+ testRendering("default", "default", "________8AADVVVVAAAAAAAAAAAA____________AAAAAAAAAAAA____VVVVAAAA____AAAAVVVVAAAAAADgAAD_________");
+ testRendering("default", "pmw3", "b_gAb9-HV8AG__AHV_AG7kAGX-AGV-AOX8APX_gOX-AP28AO_-APX-APXeAOUQAOX-AOX_gP_-AfAAAAAADgAAD_q97_____");
+ testRendering("splits_two_rows", "celeste", "b4AAYAAH________bwAA4AAHb-AA4AAHb-AAYAAHbwAA____bwAAYAAH_9VV____b_AAYAAH__wA____b_AAYAAPb8AAYAAP");
+ testRendering("splits_with_labels", "celeste", "AAAAAABMAADnAADnAABH________WAAHfwAHfwAHTwAHVVVVAAAA0gAH_wAH_wAH3wAH_________IAP34AP34AP38APAAAA");
+ testRendering("title_centered_no_game_icon", "celeste", "________MzMzADAAADwAADwA________VX1VADwAAAAAAAAA____ABkAAP8DAP8DAP8DAP8D________V_1XAGAAAAAAAAAA");
+ testRendering("title_centered_with_game_icon", "celeste", "________AiIiQDAAYDwAYDwA________YDwAYDwAYAAAYAAAd393YBkAYL8DYP8DYP8DYP8D________d_1XYGAAAAAAAAAA");
+ testRendering("title_left_no_attempt_count", "celeste", "________MzMz4AAA8AAA-AAA_________VVV-AAAAAAAAAAA____GQAA_wAA_wAA_wAA_wAA________f_1XIAAAAAAAAAAA");
after(async () => {
await driver.quit();