mirror of
https://github.com/ApfelTeeSaft/LiveSplitOne.git
synced 2026-08-26 19:23:40 +00:00
Convert Everything to CSS Modules (#1032)
This converts the remainder of the CSS files to CSS modules. The following are not converted: 1. `variables.scss`: I'm not entirely sure if variables can be converted to CSS modules. The documentation states you might need PostCSS for it. However, we probably may want to look into CSS variables anyway. So this would be done in a subsequent PR. 2. `main.scss`: This declares global styles. I'm not sure if it's possible to convert this as this is needed from the very beginning, before any React is even loaded.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { toast } from "react-toastify";
|
||||
import { LSOCommandSink } from "../ui/LSOCommandSink";
|
||||
import { LSOCommandSink } from "../util/LSOCommandSink";
|
||||
import { ServerProtocol } from "../livesplit-core/livesplit_core";
|
||||
import { Event } from "../livesplit-core";
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
$icon-size: 40px;
|
||||
$title-font-size: 40px;
|
||||
$build-version-font-size: 12px;
|
||||
$link-color: #56b0ff;
|
||||
|
||||
.about {
|
||||
max-width: 700px;
|
||||
@@ -24,22 +23,18 @@ $link-color: #56b0ff;
|
||||
margin-bottom: variables.$ui-large-margin;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.livesplitTitle {
|
||||
.liveSplitTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: variables.$ui-margin;
|
||||
}
|
||||
|
||||
.livesplitIcon {
|
||||
.liveSplitIcon {
|
||||
height: $icon-size;
|
||||
}
|
||||
|
||||
@@ -55,8 +50,9 @@ $link-color: #56b0ff;
|
||||
.changelog {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
>div {
|
||||
margin: variables.$ui-large-margin 0 variables.$ui-large-margin variables.$ui-large-margin;
|
||||
> div {
|
||||
margin: variables.$ui-large-margin 0 variables.$ui-large-margin
|
||||
variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
@use "variables.icss";
|
||||
|
||||
// FIXME: This should be a component.
|
||||
.group {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
font-size: 18px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.pressed,
|
||||
.pressed:hover {
|
||||
background: variables.$button-active-color;
|
||||
}
|
||||
|
||||
.pressed.disabled {
|
||||
background: variables.$button-disabled-color;
|
||||
color: variables.$button-disabled-text-color;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.tabBar {
|
||||
composes: group;
|
||||
display: flex;
|
||||
|
||||
> button {
|
||||
font-size: 15px;
|
||||
min-height: 30px;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ dialog {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
>* {
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.text-font {
|
||||
font-family: var(--text-font-family);
|
||||
font-style: var(--text-font-style);
|
||||
font-weight: var(--text-font-weight);
|
||||
font-stretch: var(--text-font-stretch);
|
||||
}
|
||||
|
||||
.times-font {
|
||||
font-family: var(--times-font-family);
|
||||
font-style: var(--times-font-style);
|
||||
font-weight: var(--times-font-weight);
|
||||
font-stretch: var(--times-font-stretch);
|
||||
}
|
||||
|
||||
.timer-font {
|
||||
font-family: var(--timer-font-family);
|
||||
font-style: var(--timer-font-style);
|
||||
font-weight: var(--timer-font-weight);
|
||||
font-stretch: var(--timer-font-stretch);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
@use "Font";
|
||||
|
||||
.resizableLayout {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
.layoutEditorOuter {
|
||||
display: inline-flex;
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.layoutEditorInnerContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layoutEditorInner {
|
||||
width: variables.$settings-table-width;
|
||||
display: inline-flex;
|
||||
margin-bottom: variables.$ui-large-margin;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btnGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
|
||||
button {
|
||||
width: 40px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-direction: row;
|
||||
margin: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.layoutEditorComponentList {
|
||||
composes: table from "Table.module.scss";
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-left: 0;
|
||||
margin-bottom: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.layoutEditorComponent {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layoutContainer {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-left: 0;
|
||||
margin-top: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
@use "mobile";
|
||||
@use "Table";
|
||||
@use "Toggle";
|
||||
@use "variables.icss";
|
||||
|
||||
.layout-editor-outer {
|
||||
display: inline-flex;
|
||||
|
||||
@include Table.table;
|
||||
@include Toggle.toggle;
|
||||
|
||||
.layout-editor-inner {
|
||||
width: variables.$settings-table-width;
|
||||
display: inline-flex;
|
||||
margin-bottom: variables.$ui-large-margin;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
|
||||
button {
|
||||
width: 40px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-direction: row;
|
||||
margin: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-editor-component-list {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-left: 0;
|
||||
margin-bottom: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-editor-component {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-left: 0;
|
||||
margin-top: variables.$ui-large-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.layout-editor-inner-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-editor-tabs button {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
$small-font-size: 13px;
|
||||
|
||||
.leaderboardTable {
|
||||
composes: table from "Table.module.scss";
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.leaderboardRow:hover {
|
||||
background: variables.$hover-row-color !important;
|
||||
}
|
||||
|
||||
.leaderboardRankColumn {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.leaderboardTimeColumn,
|
||||
.variableColumn {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.variableColumn {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.leaderboardExpandedRow {
|
||||
& > td {
|
||||
max-width: 0;
|
||||
}
|
||||
|
||||
.runMetaTable {
|
||||
border-spacing: variables.$ui-margin 2px;
|
||||
margin-left: -(variables.$ui-margin);
|
||||
}
|
||||
}
|
||||
|
||||
.unregisteredUser {
|
||||
font-style: italic;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.filterTable {
|
||||
composes: table from "Table.module.scss";
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
padding: math.div(variables.$ui-margin, 2) variables.$ui-margin;
|
||||
background: variables.$light-row-color;
|
||||
}
|
||||
|
||||
tr:first-child > td {
|
||||
padding-top: variables.$ui-margin;
|
||||
}
|
||||
|
||||
tr:last-child > td {
|
||||
padding-bottom: variables.$ui-margin;
|
||||
}
|
||||
|
||||
> tbody.tableBody > tr {
|
||||
background-color: variables.$light-row-color;
|
||||
}
|
||||
|
||||
> thead.tableHeader > tr {
|
||||
background-color: variables.$header-row-color;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
margin: 0 0 variables.$ui-margin (-(variables.$ui-margin));
|
||||
width: calc(100% + #{variables.$ui-margin});
|
||||
}
|
||||
}
|
||||
|
||||
.subcategoryTable {
|
||||
composes: filterTable;
|
||||
> tbody > tr {
|
||||
border: 1px solid variables.$border-color;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
> td:hover {
|
||||
background: variables.$hover-row-color;
|
||||
}
|
||||
}
|
||||
|
||||
tr:first-child > td {
|
||||
padding-top: math.div(variables.$ui-margin, 2);
|
||||
}
|
||||
|
||||
tr:last-child > td {
|
||||
padding-bottom: math.div(variables.$ui-margin, 2);
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
composes: selected from "Table.module.scss";
|
||||
&:hover {
|
||||
background: variables.$selected-row-hover-color !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
$sidebar-button-size: 40px;
|
||||
|
||||
.openSidebarButton {
|
||||
position: fixed;
|
||||
bottom: variables.$ui-margin;
|
||||
left: variables.$ui-margin;
|
||||
z-index: 10;
|
||||
font-size: 24px;
|
||||
padding: 0;
|
||||
width: $sidebar-button-size;
|
||||
height: $sidebar-button-size;
|
||||
}
|
||||
|
||||
.viewContainer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
min-width: calc(100% - 2 * #{variables.$main-content-margin});
|
||||
min-height: calc(100% - 2 * #{variables.$main-content-margin});
|
||||
margin: variables.$main-content-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
display: block;
|
||||
margin: 0 0 ($sidebar-button-size + 2 * variables.$ui-margin) 0;
|
||||
min-width: 100%;
|
||||
min-height: calc(
|
||||
100% - #{$sidebar-button-size + 2 * variables.$ui-margin}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
@use "mobile";
|
||||
@use "Sidebar";
|
||||
@use "variables.icss";
|
||||
|
||||
$sidebar-button-size: 40px;
|
||||
|
||||
.livesplit-container {
|
||||
.open-sidebar-button {
|
||||
position: fixed;
|
||||
bottom: variables.$ui-margin;
|
||||
left: variables.$ui-margin;
|
||||
z-index: 10;
|
||||
font-size: 24px;
|
||||
padding: 0;
|
||||
width: $sidebar-button-size;
|
||||
height: $sidebar-button-size;
|
||||
}
|
||||
|
||||
.view-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
min-width: calc(100% - 2 * #{variables.$main-content-margin});
|
||||
min-height: calc(100% - 2 * #{variables.$main-content-margin});
|
||||
margin: variables.$main-content-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
display: block;
|
||||
margin: 0 0 ($sidebar-button-size + 2 * variables.$ui-margin) 0;
|
||||
min-width: 100%;
|
||||
min-height: calc(
|
||||
100% - #{$sidebar-button-size + 2 * variables.$ui-margin}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@use "variables.icss";
|
||||
|
||||
.livesplit-server-button {
|
||||
.liveSplitServerButton {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
min-height: 22px;
|
||||
@@ -1,22 +1,12 @@
|
||||
@use "variables.icss";
|
||||
|
||||
@mixin markdown {
|
||||
.flag {
|
||||
padding-right: 4px;
|
||||
height: 14px;
|
||||
width: 18px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
.markdown {
|
||||
span {
|
||||
line-height: 125%;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
span {
|
||||
line-height: 125%;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -40,3 +30,11 @@
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.flag {
|
||||
padding-right: 4px;
|
||||
height: 14px;
|
||||
width: 18px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
$tab-bar-height: 30px;
|
||||
$tab-width: 625px;
|
||||
$button-width: 160px;
|
||||
$segment-icon-size: 19px;
|
||||
|
||||
$mobile-game-icon-size: 100px;
|
||||
$small-font-size: 13px;
|
||||
$small-button-padding: 1px 3px 1px 3px;
|
||||
|
||||
.tableHeader {
|
||||
border-bottom: solid 1px variables.$border-color;
|
||||
|
||||
> tr {
|
||||
display: table-row;
|
||||
background: variables.$header-row-color;
|
||||
|
||||
> th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: math.div(variables.$ui-margin, 2)
|
||||
math.div(variables.$ui-margin, 2);
|
||||
display: table-cell;
|
||||
|
||||
&:first-child {
|
||||
padding-left: variables.$ui-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
|
||||
&:nth-child(1):after {
|
||||
margin: 0;
|
||||
margin-left: -4px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rulesOuter {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
background-color: variables.$light-row-color;
|
||||
width: $tab-width;
|
||||
}
|
||||
|
||||
.rulesInner {
|
||||
padding-left: variables.$ui-margin;
|
||||
padding-right: variables.$ui-margin;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.runEditorTab {
|
||||
margin-left: math.div(variables.$ui-large-margin, 2);
|
||||
min-width: $tab-width;
|
||||
box-sizing: border-box;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.videoOuterContainer {
|
||||
width: 100%;
|
||||
padding-top: calc(100% * 9 / 16);
|
||||
margin-top: math.div(variables.$ui-margin, 2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.videoInnerContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.runEditorInfo {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: variables.$ui-large-margin - math.div(
|
||||
variables.$ui-margin,
|
||||
2
|
||||
);
|
||||
|
||||
@include mobile.mobile {
|
||||
margin: variables.$ui-margin 0 0 variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.gameIconContainer {
|
||||
background-color: variables.$light-row-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
width: $button-width;
|
||||
height: $button-width;
|
||||
padding: variables.$ui-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: $mobile-game-icon-size;
|
||||
height: $mobile-game-icon-size;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gameIconImage {
|
||||
object-fit: contain;
|
||||
width: $button-width - 2 * variables.$ui-margin;
|
||||
height: $button-width - 2 * variables.$ui-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: $mobile-game-icon-size - 2 * variables.$ui-margin;
|
||||
height: $mobile-game-icon-size - 2 * variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.runEditorInfoTable {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
width: $tab-width;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
margin-left: variables.$ui-margin;
|
||||
margin-bottom: variables.$ui-large-margin - variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.infoTableRow {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
&:last-child .infoTableCell {
|
||||
padding-bottom: 0;
|
||||
|
||||
@include mobile.mobile {
|
||||
padding-bottom: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.infoTableCell {
|
||||
padding: 0 variables.$ui-margin variables.$ui-margin 0;
|
||||
flex-grow: 1;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
|
||||
@include mobile.mobile {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.runEditorTable {
|
||||
composes: table from "Table.module.scss";
|
||||
td {
|
||||
overflow: hidden;
|
||||
|
||||
> input {
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
tbody > tr > .segmentIconContainer {
|
||||
height: $segment-icon-size;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bottomSection {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.editorGroup {
|
||||
@include mobile.mobile {
|
||||
min-width: 100%;
|
||||
margin-top: variables.$ui-large-margin - variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.sideButtonsOuter {
|
||||
margin-bottom: variables.$ui-margin;
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tabBar {
|
||||
composes: tabBar from "ButtonGroup.module.scss";
|
||||
margin-left: math.div(variables.$ui-large-margin, 2);
|
||||
height: $tab-bar-height;
|
||||
|
||||
@include mobile.mobile {
|
||||
height: inherit;
|
||||
margin-left: 0;
|
||||
|
||||
button {
|
||||
font-size: $small-font-size;
|
||||
padding: $small-button-padding;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sideButtonsInner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
margin-top: $tab-bar-height;
|
||||
margin-right: math.div(variables.$ui-large-margin, 2);
|
||||
width: $button-width;
|
||||
|
||||
button {
|
||||
font-size: 15px;
|
||||
width: $button-width;
|
||||
min-height: 30px;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: calc(100% - 2 * #{variables.$ui-margin});
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-top: 0;
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.bestSegmentTime {
|
||||
// FIXME: important
|
||||
color: hsla(50, 100%, 50%, 1) !important;
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "Markdown";
|
||||
@use "mobile";
|
||||
@use "Table";
|
||||
@use "Toggle";
|
||||
@use "variables.icss";
|
||||
|
||||
$tab-bar-height: 30px;
|
||||
$tab-width: 625px;
|
||||
$button-width: 160px;
|
||||
$segment-icon-size: 19px;
|
||||
|
||||
$mobile-game-icon-size: 100px;
|
||||
$small-font-size: 13px;
|
||||
$small-button-padding: 1px 3px 1px 3px;
|
||||
|
||||
.run-editor {
|
||||
@include Markdown.markdown;
|
||||
@include Table.table;
|
||||
@include Toggle.toggle;
|
||||
|
||||
thead.table-header {
|
||||
border-bottom: solid 1px variables.$border-color;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
> tr > th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> tr > th:nth-child(1):after {
|
||||
margin: 0;
|
||||
margin-left: -4px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.table > .table-header > tr {
|
||||
display: table-row;
|
||||
background: variables.$header-row-color;
|
||||
|
||||
> th {
|
||||
padding: math.div(variables.$ui-margin, 2)
|
||||
math.div(variables.$ui-margin, 2);
|
||||
display: table-cell;
|
||||
|
||||
&:first-child {
|
||||
padding-left: variables.$ui-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.run-editor-additional-info {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
background-color: variables.$light-row-color;
|
||||
width: $tab-width;
|
||||
|
||||
.run-editor-rules {
|
||||
padding-left: variables.$ui-margin;
|
||||
padding-right: variables.$ui-margin;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
}
|
||||
|
||||
.run-editor-tab {
|
||||
margin-left: math.div(variables.$ui-large-margin, 2);
|
||||
min-width: $tab-width;
|
||||
box-sizing: border-box;
|
||||
|
||||
.settings-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.video-outer-container {
|
||||
width: 100%;
|
||||
padding-top: calc(100% * 9 / 16);
|
||||
margin-top: math.div(variables.$ui-margin, 2);
|
||||
position: relative;
|
||||
|
||||
.video-inner-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.run-editor-info {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: variables.$ui-large-margin - math.div(
|
||||
variables.$ui-margin,
|
||||
2
|
||||
);
|
||||
|
||||
.game-icon-container {
|
||||
background-color: variables.$light-row-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
width: $button-width;
|
||||
height: $button-width;
|
||||
padding: variables.$ui-margin;
|
||||
|
||||
.game-icon-image {
|
||||
object-fit: contain;
|
||||
width: $button-width - 2 * variables.$ui-margin;
|
||||
height: $button-width - 2 * variables.$ui-margin;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: $mobile-game-icon-size - 2 * variables.$ui-margin;
|
||||
height: $mobile-game-icon-size - 2 * variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: $mobile-game-icon-size;
|
||||
height: $mobile-game-icon-size;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.run-editor-info-table {
|
||||
margin-left: variables.$ui-large-margin;
|
||||
width: $tab-width;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.info-table-row {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.info-table-cell {
|
||||
padding: 0 variables.$ui-margin variables.$ui-margin 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.info-table-cell:last-child {
|
||||
padding-right: 0;
|
||||
|
||||
@include mobile.mobile {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.info-table-row:last-child .info-table-cell {
|
||||
padding-bottom: 0;
|
||||
|
||||
@include mobile.mobile {
|
||||
padding-bottom: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
margin-left: variables.$ui-margin;
|
||||
margin-bottom: variables.$ui-large-margin - variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
margin: variables.$ui-margin 0 0 variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
table.run-editor-table {
|
||||
.number {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
td {
|
||||
overflow: hidden;
|
||||
|
||||
> input {
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
&.segment-icon-container {
|
||||
height: $segment-icon-size;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.editor-group {
|
||||
.tab-bar {
|
||||
margin-left: math.div(variables.$ui-large-margin, 2);
|
||||
height: $tab-bar-height;
|
||||
|
||||
@include mobile.mobile {
|
||||
height: inherit;
|
||||
margin-left: 0;
|
||||
|
||||
button {
|
||||
font-size: $small-font-size;
|
||||
padding: $small-button-padding;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
min-width: 100%;
|
||||
margin-top: variables.$ui-large-margin - variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.side-buttons {
|
||||
margin-bottom: variables.$ui-margin;
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
margin-top: $tab-bar-height;
|
||||
margin-right: math.div(variables.$ui-large-margin, 2);
|
||||
width: $button-width;
|
||||
|
||||
button {
|
||||
font-size: 15px;
|
||||
width: $button-width;
|
||||
min-height: 30px;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: calc(100% - 2 * #{variables.$ui-margin});
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-top: 0;
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.best-segment-time {
|
||||
color: hsla(50, 100%, 50%, 1);
|
||||
}
|
||||
|
||||
.leaderboard-table {
|
||||
.leaderboard-row:hover {
|
||||
background: variables.$hover-row-color !important;
|
||||
}
|
||||
|
||||
.leaderboard-rank-column,
|
||||
.splits-download-column {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.leaderboard-time-column,
|
||||
.variable-column {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.variable-column,
|
||||
.splits-download-column {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.leaderboard-expanded-row {
|
||||
& > td {
|
||||
max-width: 0;
|
||||
}
|
||||
|
||||
.run-meta-table {
|
||||
border-spacing: variables.$ui-margin 2px;
|
||||
margin-left: -(variables.$ui-margin);
|
||||
}
|
||||
}
|
||||
|
||||
.unregistered-user {
|
||||
font-style: italic;
|
||||
color: silver;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-table {
|
||||
width: 100%;
|
||||
|
||||
&.table {
|
||||
td {
|
||||
padding: math.div(variables.$ui-margin, 2) variables.$ui-margin;
|
||||
}
|
||||
|
||||
tr:first-child > td {
|
||||
padding-top: variables.$ui-margin;
|
||||
}
|
||||
|
||||
tr:last-child > td {
|
||||
padding-bottom: variables.$ui-margin;
|
||||
}
|
||||
|
||||
> tbody.table-body > tr {
|
||||
background-color: variables.$light-row-color;
|
||||
}
|
||||
|
||||
> thead.table-header > tr {
|
||||
background-color: variables.$header-row-color;
|
||||
}
|
||||
|
||||
&.subcategory-table {
|
||||
> tbody > tr {
|
||||
border: 1px solid variables.$border-color;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr:first-child > td {
|
||||
padding-top: math.div(variables.$ui-margin, 2);
|
||||
}
|
||||
|
||||
tr:last-child > td {
|
||||
padding-bottom: math.div(variables.$ui-margin, 2);
|
||||
}
|
||||
|
||||
> tbody.table-body > tr {
|
||||
> td:hover {
|
||||
background: variables.$hover-row-color;
|
||||
}
|
||||
|
||||
> td.selected:hover {
|
||||
background: variables.$selected-row-hover-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
margin: 0 0 variables.$ui-margin (-(variables.$ui-margin));
|
||||
width: calc(100% + #{variables.$ui-margin});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@use "mobile";
|
||||
@use "Table";
|
||||
|
||||
.settings-editor {
|
||||
@include Table.table;
|
||||
|
||||
@include mobile.mobile {
|
||||
.settings-table {
|
||||
width: 100%;
|
||||
|
||||
& > tbody > tr > td:nth-child(1) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "variables.icss";
|
||||
|
||||
$h1-font-size: 24px;
|
||||
$h2-font-size: 22px;
|
||||
|
||||
.sidebarOverlay {
|
||||
z-index: 3 !important;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
background: variables.$sidebar-background-color;
|
||||
padding: variables.$ui-margin;
|
||||
width: 250px;
|
||||
z-index: 4 !important;
|
||||
|
||||
hr {
|
||||
border-color: variables.$border-color;
|
||||
margin: variables.$ui-margin 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: $h1-font-size;
|
||||
margin-top: variables.$ui-large-margin;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $h2-font-size;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.modifiedIcon {
|
||||
position: absolute;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "Toggle";
|
||||
@use "variables.icss";
|
||||
|
||||
$h1-font-size: 24px;
|
||||
$h2-font-size: 22px;
|
||||
|
||||
.sidebar-overlay {
|
||||
z-index: 3 !important;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: variables.$ui-margin;
|
||||
background: variables.$sidebar-background-color;
|
||||
padding: variables.$ui-margin;
|
||||
width: 250px;
|
||||
z-index: 4 !important;
|
||||
|
||||
@include Toggle.toggle;
|
||||
|
||||
> div > div.small {
|
||||
display: flex;
|
||||
|
||||
> button {
|
||||
width: 50%;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-buttons {
|
||||
display: contents;
|
||||
hr {
|
||||
border-color: variables.$border-color;
|
||||
margin: variables.$ui-margin 0;
|
||||
|
||||
&.livesplit-title-separator {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: $h1-font-size;
|
||||
margin-top: variables.$ui-large-margin;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: $h2-font-size;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.livesplit-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: variables.$ui-margin;
|
||||
margin-top: variables.$ui-margin;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.livesplit-icon {
|
||||
height: 40px;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modified-icon {
|
||||
position: absolute;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.choose-comparison {
|
||||
padding: 0 6px 0 6px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
appearance: none;
|
||||
background: variables.$button-middle-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: "fira", sans-serif;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
$loading-text-font-size: 40px;
|
||||
$splits-row-width: 500px;
|
||||
$splits-row-height: 40px;
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
font-size: $loading-text-font-size;
|
||||
}
|
||||
|
||||
.loadingText {
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
|
||||
.splitsSelectionContainer {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mainActions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
button {
|
||||
margin-top: 0;
|
||||
margin-bottom: math.div(variables.$ui-large-margin, 2);
|
||||
margin-right: variables.$ui-margin;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-top: variables.$ui-margin;
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.splitsTable {
|
||||
background-color: variables.$dark-row-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
margin: math.div(variables.$ui-large-margin, 2) 0;
|
||||
width: fit-content;
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.splitsRow {
|
||||
display: flex;
|
||||
gap: variables.$ui-large-margin;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
padding: variables.$ui-margin;
|
||||
height: $splits-row-height;
|
||||
width: $splits-row-width;
|
||||
|
||||
&:nth-of-type(odd) {
|
||||
background-color: variables.$light-row-color;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: variables.$selected-row-color;
|
||||
}
|
||||
|
||||
&.selected .splitsRowButtons button {
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: $splits-row-height + 2 * variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.splitsRowButtons {
|
||||
display: contents;
|
||||
flex-shrink: 0;
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
opacity: 50%;
|
||||
margin: 0;
|
||||
transition: 0.3s;
|
||||
color: white;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.splitsTitleText {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.splitsText {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.splitsGame {
|
||||
margin-bottom: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile";
|
||||
@use "variables.icss";
|
||||
@use "Table";
|
||||
|
||||
$loading-text-font-size: 40px;
|
||||
$splits-row-width: 500px;
|
||||
$splits-row-height: 40px;
|
||||
|
||||
.splits-selection {
|
||||
.loading {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
font-size: $loading-text-font-size;
|
||||
|
||||
.loading-text {
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.splits-selection-container {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
|
||||
.main-actions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
button {
|
||||
margin-top: 0;
|
||||
margin-bottom: math.div(variables.$ui-large-margin, 2);
|
||||
margin-right: variables.$ui-margin;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
margin-top: variables.$ui-margin;
|
||||
margin-left: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.splits-table {
|
||||
background-color: variables.$dark-row-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
margin: math.div(variables.$ui-large-margin, 2) 0;
|
||||
width: fit-content;
|
||||
|
||||
.splits-row {
|
||||
display: flex;
|
||||
gap: variables.$ui-large-margin;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
padding: variables.$ui-margin;
|
||||
height: $splits-row-height;
|
||||
width: $splits-row-width;
|
||||
|
||||
&:nth-of-type(odd) {
|
||||
background-color: variables.$light-row-color;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: variables.$selected-row-color;
|
||||
}
|
||||
|
||||
.splits-title-text {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.splits-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.splits-game {
|
||||
margin-bottom: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .splits-row-buttons button {
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
.splits-row-buttons {
|
||||
display: contents;
|
||||
flex-shrink: 0;
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
opacity: 50%;
|
||||
margin: 0;
|
||||
transition: 0.3s;
|
||||
color: white;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: $splits-row-height + 2 * variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
.table {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
display: table;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-collapse: collapse;
|
||||
|
||||
> tbody {
|
||||
> tr.explicitEven,
|
||||
> :nth-child(even) {
|
||||
display: table-row;
|
||||
background: variables.$dark-row-color;
|
||||
}
|
||||
|
||||
> tr.explicitOdd,
|
||||
> :nth-child(odd) {
|
||||
display: table-row;
|
||||
background: variables.$light-row-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableBody {
|
||||
display: table-row-group;
|
||||
|
||||
> tr {
|
||||
display: table-row;
|
||||
background: variables.$dark-row-color;
|
||||
|
||||
> td {
|
||||
padding: math.div(variables.$ui-margin, 2)
|
||||
math.div(variables.$ui-margin, 2);
|
||||
display: table-cell;
|
||||
|
||||
&:first-child {
|
||||
padding-left: variables.$ui-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr > td > input {
|
||||
margin-right: -40px;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input.textBox {
|
||||
font-family: inherit;
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.25);
|
||||
}
|
||||
|
||||
input[Type="text"] {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
select {
|
||||
background: variables.$dark-row-color
|
||||
url("data:image/svg+xml;charset=UTF-8,%3Csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolygon points='0,4 2,4 5,7 8,4 10,4 5,9' fill='%23fff'/%3E%3C/svg%3E")
|
||||
no-repeat right 4px center;
|
||||
font-size: 15px;
|
||||
border: 1px solid variables.$border-color;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-family: inherit;
|
||||
padding-left: 4px;
|
||||
padding-right: 20px;
|
||||
border-radius: 0;
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.number {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: variables.$selected-row-color !important;
|
||||
}
|
||||
|
||||
.settingsTable {
|
||||
width: variables.$settings-table-width;
|
||||
|
||||
tr {
|
||||
height: 30px;
|
||||
|
||||
td:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settingsValueBox {
|
||||
width: math.div(variables.$settings-table-width, 2) - 2 *
|
||||
variables.$ui-margin;
|
||||
display: grid;
|
||||
column-gap: variables.$ui-margin;
|
||||
row-gap: variables.$ui-margin;
|
||||
grid-template-columns: 100%;
|
||||
|
||||
input {
|
||||
height: variables.$settings-row-height;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
height: variables.$settings-row-height;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
&.optionalValue {
|
||||
grid-template-columns: max-content 1fr;
|
||||
}
|
||||
|
||||
&.twoColors {
|
||||
grid-template-columns: 1fr 45px 45px;
|
||||
}
|
||||
|
||||
&.oneColor {
|
||||
grid-template-columns: 1fr 45px;
|
||||
}
|
||||
|
||||
&.removableString {
|
||||
grid-template-columns: 1fr 20px;
|
||||
|
||||
.trash {
|
||||
// FIXME: Deduplicate
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
min-height: variables.$settings-row-height;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile";
|
||||
@use "variables.icss";
|
||||
|
||||
@mixin table {
|
||||
.table {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
display: table;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-collapse: collapse;
|
||||
|
||||
.table-row-even {
|
||||
display: table-row;
|
||||
background: variables.$dark-row-color !important;
|
||||
}
|
||||
|
||||
.table-row-odd {
|
||||
display: table-row;
|
||||
background: variables.$light-row-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.table-body {
|
||||
display: table-row-group;
|
||||
|
||||
> tr {
|
||||
display: table-row;
|
||||
background: variables.$dark-row-color;
|
||||
|
||||
> td {
|
||||
padding: math.div(variables.$ui-margin, 2)
|
||||
math.div(variables.$ui-margin, 2);
|
||||
display: table-cell;
|
||||
|
||||
&:first-child {
|
||||
padding-left: variables.$ui-margin;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: variables.$ui-margin;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(odd) {
|
||||
background: variables.$light-row-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.number {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: variables.$selected-row-color !important;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
|
||||
> button {
|
||||
font-size: 15px;
|
||||
min-height: 30px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
tr > td > input {
|
||||
margin-right: -40px;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input.text-box {
|
||||
font-family: inherit;
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.25);
|
||||
}
|
||||
|
||||
input[Type="text"] {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
select {
|
||||
background: variables.$dark-row-color
|
||||
url("data:image/svg+xml;charset=UTF-8,%3Csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpolygon points='0,4 2,4 5,7 8,4 10,4 5,9' fill='%23fff'/%3E%3C/svg%3E")
|
||||
no-repeat right 4px center;
|
||||
font-size: 15px;
|
||||
border: 1px solid variables.$border-color;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-family: inherit;
|
||||
padding-left: 4px;
|
||||
padding-right: 20px;
|
||||
border-radius: 0;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.settings-table {
|
||||
width: variables.$settings-table-width;
|
||||
|
||||
tr {
|
||||
height: 30px;
|
||||
|
||||
td:first-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-value-box {
|
||||
width: math.div(variables.$settings-table-width, 2) - 2 *
|
||||
variables.$ui-margin;
|
||||
display: grid;
|
||||
column-gap: variables.$ui-margin;
|
||||
row-gap: variables.$ui-margin;
|
||||
grid-template-columns: 100%;
|
||||
|
||||
input {
|
||||
height: variables.$settings-row-height;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
height: variables.$settings-row-height;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
&.optional-value {
|
||||
grid-template-columns: max-content 1fr;
|
||||
}
|
||||
|
||||
&.two-colors {
|
||||
grid-template-columns: 1fr 45px 45px;
|
||||
}
|
||||
|
||||
&.one-color {
|
||||
grid-template-columns: 1fr 45px;
|
||||
}
|
||||
|
||||
&.removable-string {
|
||||
grid-template-columns: 1fr 20px;
|
||||
|
||||
.trash {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
min-height: variables.$settings-row-height;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
.buttons {
|
||||
margin-top: variables.$ui-large-margin - math.div(variables.$ui-margin, 2);
|
||||
max-width: 2 * variables.$button-max-width +
|
||||
math.div(variables.$ui-margin, 2);
|
||||
|
||||
@include mobile.mobile {
|
||||
padding: 0 variables.$ui-margin;
|
||||
width: calc(100% - #{2 * variables.$ui-margin}) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.controlButtons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: variables.$ui-margin;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.manualGameTime {
|
||||
width: calc(100% - math.div(variables.$ui-margin, 2));
|
||||
color: variables.$button-text-color;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #aaa;
|
||||
text-align: right;
|
||||
font-family: "fira", sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
height: variables.$manual-game-time-height;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.liveSplitTitle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: variables.$ui-margin;
|
||||
margin-top: variables.$ui-margin;
|
||||
|
||||
h1 {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
+ hr {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.liveSplitIcon {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.chooseComparison {
|
||||
padding: 0 6px 0 6px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
appearance: none;
|
||||
background: variables.$button-middle-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: "fira", sans-serif;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "mobile";
|
||||
@use "variables.icss";
|
||||
|
||||
.buttons {
|
||||
margin-top: variables.$ui-large-margin - math.div(variables.$ui-margin, 2);
|
||||
max-width: 2 * variables.$button-max-width +
|
||||
math.div(variables.$ui-margin, 2);
|
||||
|
||||
.control-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: variables.$ui-margin;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.manual-game-time {
|
||||
width: calc(100% - math.div(variables.$ui-margin, 2));
|
||||
color: variables.$button-text-color;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #aaa;
|
||||
text-align: right;
|
||||
font-family: "fira", sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 25px;
|
||||
height: variables.$manual-game-time-height;
|
||||
}
|
||||
|
||||
.manual-game-time:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.manual-game-time::placeholder {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@include mobile.mobile {
|
||||
padding: 0 variables.$ui-margin;
|
||||
width: calc(100% - #{2 * variables.$ui-margin}) !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
:global(.Toastify) {
|
||||
.toastClass {
|
||||
font-family: "fira", sans-serif;
|
||||
background: rgba(28, 28, 28, 0.8) !important;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 2px solid rgba(255, 255, 255, 0.25) !important;
|
||||
border-radius: 10px !important;
|
||||
min-height: 48px !important;
|
||||
padding-right: 28px;
|
||||
|
||||
&.toastBug {
|
||||
border-color: red !important;
|
||||
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
margin-bottom: initial;
|
||||
margin-top: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.toastBody {
|
||||
margin: 5px 5px 5px 5px !important;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
@use "variables.icss";
|
||||
|
||||
@mixin toggle {
|
||||
.toggle-left {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.toggle-right {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.toggle-middle {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.button-pressed,
|
||||
.button-pressed:hover {
|
||||
background: variables.$button-active-color;
|
||||
}
|
||||
|
||||
.button-pressed.disabled {
|
||||
background: variables.$button-disabled-color;
|
||||
color: variables.$button-disabled-text-color;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@use "mobile.module.scss";
|
||||
@use "variables.icss";
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
|
||||
.tooltipText {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
background-color: rgba(28, 28, 28, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: variables.$ui-margin;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: calc(100% + variables.$ui-margin);
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s;
|
||||
transition-delay: 0.25s;
|
||||
text-wrap: initial;
|
||||
font-weight: initial;
|
||||
|
||||
@include mobile.mobile {
|
||||
left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .tooltipText {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.contextMenuItem .tooltipText {
|
||||
bottom: initial;
|
||||
left: calc(100% + variables.$ui-margin);
|
||||
margin-left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
@use "mobile";
|
||||
@use "variables.icss";
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-text {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
background-color: rgba(28, 28, 28, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: variables.$ui-margin;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: calc(100% + variables.$ui-margin);
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s;
|
||||
transition-delay: 0.25s;
|
||||
text-wrap: initial;
|
||||
font-weight: initial;
|
||||
|
||||
@include mobile.mobile {
|
||||
left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.contextmenu-item.tooltip .tooltip-text {
|
||||
bottom: initial;
|
||||
left: calc(100% + variables.$ui-margin);
|
||||
margin-left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltip-text {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
body.browser-source {
|
||||
&,
|
||||
.livesplit-container,
|
||||
.buttons {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.buttons,
|
||||
.sidebar-button {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
+20
-69
@@ -1,6 +1,5 @@
|
||||
@use "sass:math";
|
||||
|
||||
@use "browser_source";
|
||||
@use "variables.icss";
|
||||
|
||||
@font-face {
|
||||
@@ -22,61 +21,15 @@ body {
|
||||
background: variables.$main-background-color;
|
||||
}
|
||||
|
||||
.Toastify {
|
||||
.toast-class {
|
||||
font-family: "fira", sans-serif;
|
||||
background: rgba(28, 28, 28, 0.8) !important;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 2px solid rgba(255, 255, 255, 0.25) !important;
|
||||
border-radius: 10px !important;
|
||||
min-height: 48px !important;
|
||||
|
||||
&.toast-bug {
|
||||
border-color: red !important;
|
||||
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
margin: 5px 5px 5px 5px !important;
|
||||
}
|
||||
|
||||
.toast-class > button {
|
||||
margin-bottom: initial;
|
||||
margin-top: initial;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
td#dif {
|
||||
border-right: 10px solid transparent;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0 2px;
|
||||
}
|
||||
|
||||
button:active,
|
||||
button:active:hover {
|
||||
background: variables.$button-active-color;
|
||||
}
|
||||
|
||||
button:focus,
|
||||
div:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: variables.$button-hover-color;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -97,28 +50,30 @@ button {
|
||||
font-family: "fira", sans-serif;
|
||||
min-height: variables.$button-height;
|
||||
padding: 5px variables.$ui-margin;
|
||||
|
||||
&:disabled,
|
||||
&:disabled:active {
|
||||
background: variables.$button-disabled-color;
|
||||
color: variables.$button-disabled-text-color;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: variables.$button-hover-color;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:active:hover {
|
||||
background: variables.$button-active-color;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: hsla(50, 100%, 50%, 1);
|
||||
color: #56b0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button:disabled,
|
||||
button:disabled:active {
|
||||
background: variables.$button-disabled-color;
|
||||
color: variables.$button-disabled-text-color;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.chrome-picker {
|
||||
font-family: inherit !important;
|
||||
margin-top: 5px;
|
||||
margin-left: -50%;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* WebKit Scrollbars */
|
||||
@@ -152,8 +107,4 @@ button:disabled:active {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 50px;
|
||||
|
||||
.initial-load-text {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
@mixin mobile {
|
||||
.is-mobile & {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
<body>
|
||||
<div id="base">
|
||||
<div class="initial-load">
|
||||
<div class="initial-load-text">Loading...</div>
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { preloadWasm } from "./livesplit-core/preload";
|
||||
|
||||
// FIXME: Remove the polyfill later.
|
||||
// https://caniuse.com/mdn-javascript_statements_using
|
||||
|
||||
if (typeof Symbol.dispose !== "symbol") {
|
||||
Object.defineProperty(Symbol, "dispose", {
|
||||
|
||||
+5
-4
@@ -1,6 +1,8 @@
|
||||
import * as React from "react";
|
||||
import { Option } from "../util/OptionUtil";
|
||||
|
||||
import * as runEditorClasses from "../css/RunEditor.module.scss";
|
||||
|
||||
export function resolveEmbed(uri: string): Option<React.JSX.Element> {
|
||||
const youtube = tryYoutubeFromUri(uri);
|
||||
if (youtube != null) {
|
||||
@@ -47,14 +49,13 @@ function tryTwitchFromUri(uri: string): Option<React.JSX.Element> {
|
||||
|
||||
function videoIframe(videoSource: string): React.JSX.Element {
|
||||
return (
|
||||
<div className="video-outer-container">
|
||||
<div className="video-inner-container">
|
||||
<div className={runEditorClasses.videoOuterContainer}>
|
||||
<div className={runEditorClasses.videoInnerContainer}>
|
||||
<iframe
|
||||
id="ytplayer"
|
||||
itemType="text/html"
|
||||
src={videoSource}
|
||||
allowFullScreen
|
||||
frameBorder="0"
|
||||
frameBorder={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+12
-19
@@ -43,7 +43,7 @@ import * as Storage from "../storage";
|
||||
import { UrlCache } from "../util/UrlCache";
|
||||
import { ServerProtocol, WebRenderer } from "../livesplit-core/livesplit_core";
|
||||
import { LiveSplitServer } from "../api/LiveSplitServer";
|
||||
import { LSOCommandSink } from "./LSOCommandSink";
|
||||
import { LSOCommandSink } from "../util/LSOCommandSink";
|
||||
import { DialogContainer } from "./components/Dialog";
|
||||
import { createHotkeys, HotkeyImplementation } from "../platform/Hotkeys";
|
||||
import { Menu } from "lucide-react";
|
||||
@@ -51,7 +51,9 @@ import { Menu } from "lucide-react";
|
||||
import * as variables from "../css/variables.icss.scss";
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import "../css/LiveSplit.scss";
|
||||
import * as classes from "../css/LiveSplit.module.scss";
|
||||
import * as sidebarClasses from "../css/Sidebar.module.scss";
|
||||
import * as toastClasses from "../css/Toast.module.scss";
|
||||
|
||||
const buttonHeight = parseFloat(variables.buttonHeight);
|
||||
const largeMargin = parseFloat(variables.largeMargin);
|
||||
@@ -100,7 +102,6 @@ export interface Props {
|
||||
|
||||
export interface State {
|
||||
hotkeySystem: HotkeyImplementation;
|
||||
isBrowserSource: boolean;
|
||||
isDesktop: boolean;
|
||||
layout: Layout;
|
||||
layoutState: LayoutState;
|
||||
@@ -207,7 +208,6 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
const isDesktop = this.isDesktopQuery.matches;
|
||||
const isBrowserSource = !!(window as any).obsstudio;
|
||||
|
||||
const renderer = new WebRenderer();
|
||||
renderer
|
||||
@@ -215,8 +215,7 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
.setAttribute("style", "width: inherit; height: inherit;");
|
||||
|
||||
this.state = {
|
||||
isDesktop: isDesktop && !isBrowserSource,
|
||||
isBrowserSource,
|
||||
isDesktop,
|
||||
layout,
|
||||
layoutState: LayoutState.new(),
|
||||
layoutUrlCache: new UrlCache(),
|
||||
@@ -296,10 +295,6 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
this.isDesktopQuery.addEventListener("change", this.mediaQueryChanged);
|
||||
|
||||
if (this.state.isBrowserSource) {
|
||||
document.body.className = "browser-source";
|
||||
}
|
||||
|
||||
this.handleAutomaticResize();
|
||||
|
||||
const { serviceWorker } = navigator;
|
||||
@@ -466,8 +461,8 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
/>
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
toastClassName="toast-class"
|
||||
className="toast-body"
|
||||
toastClassName={toastClasses.toastClass}
|
||||
className={toastClasses.toastBody}
|
||||
theme="dark"
|
||||
/>
|
||||
</>
|
||||
@@ -486,20 +481,19 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
open={this.state.sidebarOpen}
|
||||
transitions={this.state.sidebarTransitionsEnabled}
|
||||
onSetOpen={(e) => this.onSetSidebarOpen(e)}
|
||||
sidebarClassName="sidebar"
|
||||
contentClassName="livesplit-container"
|
||||
overlayClassName="sidebar-overlay"
|
||||
sidebarClassName={sidebarClasses.sidebar}
|
||||
overlayClassName={sidebarClasses.sidebarOverlay}
|
||||
>
|
||||
{!this.state.isDesktop && !this.state.sidebarOpen && (
|
||||
<button
|
||||
aria-label="Open Sidebar"
|
||||
className="open-sidebar-button"
|
||||
className={classes.openSidebarButton}
|
||||
onClick={() => this.onSetSidebarOpen(true)}
|
||||
>
|
||||
<Menu size={30} strokeWidth={2.5} />
|
||||
</button>
|
||||
)}
|
||||
<div className="view-container">{renderedView}</div>
|
||||
<div className={classes.viewContainer}>{renderedView}</div>
|
||||
</Sidebar>
|
||||
</div>
|
||||
);
|
||||
@@ -787,8 +781,7 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private mediaQueryChanged() {
|
||||
const isDesktop =
|
||||
this.isDesktopQuery.matches && !this.state.isBrowserSource;
|
||||
const isDesktop = this.isDesktopQuery.matches;
|
||||
this.setState({
|
||||
isDesktop,
|
||||
layoutWidth: isDesktop
|
||||
|
||||
@@ -3,6 +3,8 @@ import { expect } from "../../../util/OptionUtil";
|
||||
import { AccuracyJson } from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function Accuracy<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -13,7 +15,7 @@ export function Accuracy<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { expect } from "../../../util/OptionUtil";
|
||||
import { Alignment } from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function Alignment<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -13,7 +15,7 @@ export function Alignment<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { SettingValueFactory } from ".";
|
||||
import { ColorPicker } from "../ColorPicker";
|
||||
import { Switch } from "../Switch";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function Color<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -14,7 +16,7 @@ export function Color<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<ColorPicker
|
||||
color={value}
|
||||
setColor={(color) =>
|
||||
@@ -62,7 +64,9 @@ export function OptionalColor<T>({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-value-box optional-value">
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.optionalValue}`}
|
||||
>
|
||||
<Switch
|
||||
checked={value !== null}
|
||||
setIsChecked={(checked) => {
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function ColumnKind<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -18,7 +20,7 @@ export function ColumnKind<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
@@ -47,7 +49,7 @@ export function ColumnStartWith<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
@@ -80,7 +82,7 @@ export function ColumnUpdateWith<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
@@ -118,7 +120,7 @@ export function ColumnUpdateTrigger<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { expect } from "../../../util/OptionUtil";
|
||||
import { DigitsFormatJson } from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function DigitsFormat<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -13,7 +15,7 @@ export function DigitsFormat<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { SettingValueFactory } from ".";
|
||||
import { Switch } from "../Switch";
|
||||
import * as FontList from "../../../util/FontList";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function Font<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -74,7 +76,7 @@ export function Font<T>({
|
||||
} else {
|
||||
children.push(
|
||||
<input
|
||||
className="text-box"
|
||||
className={tableClasses.textBox}
|
||||
value={family}
|
||||
onChange={(e) =>
|
||||
setValue(
|
||||
@@ -173,5 +175,11 @@ export function Font<T>({
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="settings-value-box optional-value">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.optionalValue}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import { SettingValueFactory } from ".";
|
||||
import { assertNever, expect, Option } from "../../../util/OptionUtil";
|
||||
import { ColorPicker } from "../ColorPicker";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function Gradient<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -118,11 +120,23 @@ export function Gradient<T>({
|
||||
}
|
||||
|
||||
if (color2) {
|
||||
return <div className="settings-value-box two-colors">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.twoColors}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else if (color1) {
|
||||
return <div className="settings-value-box one-color">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.oneColor}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div className="settings-value-box">{children}</div>;
|
||||
return <div className={tableClasses.settingsValueBox}>{children}</div>;
|
||||
}
|
||||
}
|
||||
export function DeltaGradient<T>({
|
||||
@@ -240,11 +254,23 @@ export function DeltaGradient<T>({
|
||||
}
|
||||
|
||||
if (color2) {
|
||||
return <div className="settings-value-box two-colors">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.twoColors}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else if (color1) {
|
||||
return <div className="settings-value-box one-color">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.oneColor}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div className="settings-value-box">{children}</div>;
|
||||
return <div className={tableClasses.settingsValueBox}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,10 +401,22 @@ export function ListGradient<T>({
|
||||
}
|
||||
|
||||
if (color2) {
|
||||
return <div className="settings-value-box two-colors">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.twoColors}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else if (color1) {
|
||||
return <div className="settings-value-box one-color">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.oneColor}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div className="settings-value-box">{children}</div>;
|
||||
return <div className={tableClasses.settingsValueBox}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Option, expect } from "../../util/OptionUtil";
|
||||
import { hotkeySystem } from "../LiveSplit";
|
||||
import { Option, expect } from "../../../util/OptionUtil";
|
||||
import { hotkeySystem } from "../../LiveSplit";
|
||||
import { Circle, Trash } from "lucide-react";
|
||||
|
||||
import * as classes from "../../css/HotkeyButton.module.scss";
|
||||
import * as classes from "../../../css/HotkeyButton.module.scss";
|
||||
import * as tooltipClasses from "../../../css/Tooltip.module.scss";
|
||||
|
||||
function resolveKey(keyCode: string): Promise<string> | string {
|
||||
return expect(
|
||||
@@ -151,11 +152,11 @@ export function HotkeyButton({
|
||||
return (
|
||||
<div className={classes.hotkeyBox}>
|
||||
<button
|
||||
className={`tooltip ${listener != null ? classes.focused : ""}`}
|
||||
className={`${tooltipClasses.tooltip} ${listener != null ? classes.focused : ""}`}
|
||||
onClick={focusButton}
|
||||
>
|
||||
{buttonText}
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Click to record a hotkey. You may also use buttons on a
|
||||
gamepad. Global hotkeys are currently not possible. Gamepad
|
||||
buttons work globally.
|
||||
@@ -7,6 +7,9 @@ import { UrlCache } from "../../../util/UrlCache";
|
||||
import { toast } from "react-toastify";
|
||||
import { FILE_EXT_IMAGES, openFileAsArrayBuffer } from "../../../util/FileUtil";
|
||||
|
||||
import * as colorPickerClasses from "../../../css/ColorPicker.module.scss";
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function LayoutBackground<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -98,7 +101,7 @@ export function LayoutBackground<T>({
|
||||
const imageUrl = editorUrlCache.cache(imageId);
|
||||
children.push(
|
||||
<div
|
||||
className="color-picker-button"
|
||||
className={colorPickerClasses.colorPickerButton}
|
||||
style={{
|
||||
background: imageUrl
|
||||
? `url("${imageUrl}") center / cover`
|
||||
@@ -227,10 +230,22 @@ export function LayoutBackground<T>({
|
||||
}
|
||||
|
||||
if (color2) {
|
||||
return <div className="settings-value-box two-colors">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.twoColors}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else if (color1 || type === "Image") {
|
||||
return <div className="settings-value-box one-color">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.oneColor}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <div className="settings-value-box">{children}</div>;
|
||||
return <div className={tableClasses.settingsValueBox}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { expect } from "../../../util/OptionUtil";
|
||||
import { LayoutDirection } from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function LayoutDirection<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -13,7 +15,7 @@ export function LayoutDirection<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as React from "react";
|
||||
import { Option } from "../../../util/OptionUtil";
|
||||
import { LiveSplitServer } from "../../../api/LiveSplitServer";
|
||||
|
||||
import "../../../css/LiveSplitServerButton.scss";
|
||||
import * as classes from "../../../css/LiveSplitServerButton.module.scss";
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function ServerConnectionButton({
|
||||
value,
|
||||
@@ -15,9 +16,9 @@ export function ServerConnectionButton({
|
||||
connectOrDisconnect: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<button
|
||||
className="livesplit-server-button"
|
||||
className={classes.liveSplitServerButton}
|
||||
onClick={connectOrDisconnect}
|
||||
>
|
||||
{(() => {
|
||||
|
||||
@@ -3,6 +3,9 @@ import { SettingValueFactory } from ".";
|
||||
import { Trash } from "lucide-react";
|
||||
import { Switch } from "../Switch";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
import * as tooltipClasses from "../../../css/Tooltip.module.scss";
|
||||
|
||||
export function String<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -13,9 +16,9 @@ export function String<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<input
|
||||
className="text-box"
|
||||
className={tableClasses.textBox}
|
||||
value={value}
|
||||
onChange={(e) => setValue(factory.fromString(e.target.value))}
|
||||
/>
|
||||
@@ -48,7 +51,7 @@ export function OptionalString<T>({
|
||||
if (value !== null) {
|
||||
children.push(
|
||||
<input
|
||||
className="text-box"
|
||||
className={tableClasses.textBox}
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
setValue(factory.fromOptionalString(e.target.value))
|
||||
@@ -57,7 +60,13 @@ export function OptionalString<T>({
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="settings-value-box optional-value">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.optionalValue}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RemovableString<T>({
|
||||
@@ -70,9 +79,11 @@ export function RemovableString<T>({
|
||||
factory: SettingValueFactory<T>;
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box removable-string">
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.removableString}`}
|
||||
>
|
||||
<input
|
||||
className="text-box"
|
||||
className={tableClasses.textBox}
|
||||
value={value ?? ""}
|
||||
onChange={(e) => {
|
||||
if (factory.fromRemovableString) {
|
||||
@@ -83,7 +94,7 @@ export function RemovableString<T>({
|
||||
}}
|
||||
/>
|
||||
<Trash
|
||||
className="trash"
|
||||
className={tableClasses.trash}
|
||||
strokeWidth={2.5}
|
||||
size={20}
|
||||
onClick={() => {
|
||||
@@ -110,7 +121,7 @@ export function Comparison<T>({
|
||||
allComparisons: string[];
|
||||
}) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value ?? ""}
|
||||
onChange={(e) => {
|
||||
@@ -143,10 +154,13 @@ export function CustomVariable<T>({
|
||||
}) {
|
||||
if (allVariables.size === 0) {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<span className="tooltip" style={{ textAlign: "center" }}>
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<span
|
||||
className={tooltipClasses.tooltip}
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
No variables available
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Custom variables can be defined in the Variables tab
|
||||
when editing splits. Additional custom variables can be
|
||||
provided automatically by auto splitters.
|
||||
@@ -156,7 +170,7 @@ export function CustomVariable<T>({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value ?? ""}
|
||||
onChange={(e) =>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { TimingMethodJson } from "../../../livesplit-core";
|
||||
import { SettingValueFactory } from ".";
|
||||
import { Switch } from "../Switch";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
|
||||
export function OptionalTimingMethod<T>({
|
||||
value,
|
||||
setValue,
|
||||
@@ -50,5 +52,11 @@ export function OptionalTimingMethod<T>({
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="settings-value-box optional-value">{children}</div>;
|
||||
return (
|
||||
<div
|
||||
className={`${tableClasses.settingsValueBox} ${tableClasses.optionalValue}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { SettingsDescriptionValueJson } from "../../../livesplit-core";
|
||||
import { assertNever, Option } from "../../../util/OptionUtil";
|
||||
import { HotkeyButton } from "../HotkeyButton";
|
||||
import { HotkeyButton } from "./HotkeyButton";
|
||||
import { UrlCache } from "../../../util/UrlCache";
|
||||
import { LiveSplitServer } from "../../../api/LiveSplitServer";
|
||||
import { showDialog } from "../Dialog";
|
||||
@@ -9,8 +9,6 @@ import { Switch } from "../Switch";
|
||||
import { ServerConnectionButton } from "./ServerConnectionButton";
|
||||
import { LayoutBackground } from "./LayoutBackground";
|
||||
import { Font } from "./Font";
|
||||
|
||||
import "../../../css/Tooltip.scss";
|
||||
import { LayoutDirection } from "./LayoutDirection";
|
||||
import {
|
||||
ColumnKind,
|
||||
@@ -32,6 +30,9 @@ import {
|
||||
String,
|
||||
} from "./String";
|
||||
|
||||
import * as tableClasses from "../../../css/Table.module.scss";
|
||||
import * as tooltipClasses from "../../../css/Tooltip.module.scss";
|
||||
|
||||
export interface Props<T> {
|
||||
context: string;
|
||||
setValue: (index: number, value: T) => void;
|
||||
@@ -265,7 +266,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
let component;
|
||||
if ("Bool" in value) {
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<Switch
|
||||
checked={value.Bool}
|
||||
setIsChecked={(value) => {
|
||||
@@ -279,10 +280,10 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
);
|
||||
} else if ("UInt" in value) {
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<input
|
||||
type="number"
|
||||
className="number text-box"
|
||||
className={`${tableClasses.number} ${tableClasses.textBox}`}
|
||||
value={value.UInt}
|
||||
min="0"
|
||||
onChange={(e) => {
|
||||
@@ -296,10 +297,10 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
);
|
||||
} else if ("Int" in value) {
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<input
|
||||
type="number"
|
||||
className="number text-box"
|
||||
className={`${tableClasses.number} ${tableClasses.textBox}`}
|
||||
value={value.Int}
|
||||
onChange={(e) => {
|
||||
this.props.setValue(
|
||||
@@ -501,7 +502,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
value.CustomCombobox.mandatory &&
|
||||
!value.CustomCombobox.value;
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<select
|
||||
value={value.CustomCombobox.value}
|
||||
onChange={(e) => {
|
||||
@@ -524,7 +525,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
);
|
||||
} else if ("Hotkey" in value) {
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<div className={tableClasses.settingsValueBox}>
|
||||
<HotkeyButton
|
||||
value={value.Hotkey}
|
||||
setValue={(value) => {
|
||||
@@ -604,9 +605,11 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
|
||||
settingsRows.push(
|
||||
<tr key={`${this.props.context}$${valueIndex}`}>
|
||||
<td className="tooltip">
|
||||
<td className={tooltipClasses.tooltip}>
|
||||
{field.text}
|
||||
<span className="tooltip-text">{field.tooltip}</span>
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
{field.tooltip}
|
||||
</span>
|
||||
</td>
|
||||
<td>{component}</td>
|
||||
</tr>,
|
||||
@@ -614,8 +617,10 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
});
|
||||
|
||||
return (
|
||||
<table className="table settings-table">
|
||||
<tbody className="table-body">{settingsRows}</tbody>
|
||||
<table
|
||||
className={`${tableClasses.table} ${tableClasses.settingsTable}`}
|
||||
>
|
||||
<tbody className={tableClasses.tableBody}>{settingsRows}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ function renderView() {
|
||||
return (
|
||||
<div className={classes.about}>
|
||||
<div className={classes.aboutInnerContainer}>
|
||||
<div className={classes.livesplitTitle}>
|
||||
<div className={classes.liveSplitTitle}>
|
||||
<img
|
||||
className={classes.livesplitIcon}
|
||||
className={classes.liveSplitIcon}
|
||||
src={LiveSplitIcon}
|
||||
alt="LiveSplit Logo"
|
||||
/>
|
||||
@@ -95,12 +95,12 @@ function renderView() {
|
||||
|
||||
function renderSidebarContent(callbacks: Callbacks) {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>About</h1>
|
||||
<hr />
|
||||
<button onClick={(_) => callbacks.openTimerView()}>
|
||||
<ArrowLeft strokeWidth={2.5} /> Back
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { UrlCache } from "../../util/UrlCache";
|
||||
import Layout from "../components/Layout";
|
||||
import { WebRenderer } from "../../livesplit-core/livesplit_core";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { LSOCommandSink } from "../LSOCommandSink";
|
||||
import { LSOCommandSink } from "../../util/LSOCommandSink";
|
||||
import {
|
||||
ContextMenu,
|
||||
MenuItem,
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
} from "../components/ContextMenu";
|
||||
import { ArrowDown, ArrowUp, Check, Copy, Plus, Trash, X } from "lucide-react";
|
||||
|
||||
import "../../css/LayoutEditor.scss";
|
||||
import * as classes from "../../css/LayoutEditor.module.scss";
|
||||
import * as buttonGroupClasses from "../../css/ButtonGroup.module.scss";
|
||||
import * as tableClasses from "../../css/Table.module.scss";
|
||||
import * as tooltipClasses from "../../css/Tooltip.module.scss";
|
||||
|
||||
export interface Props {
|
||||
editor: LiveSplit.LayoutEditor;
|
||||
@@ -70,9 +73,9 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
|
||||
private renderView() {
|
||||
const components = this.state.editor.components.map((c, i) => {
|
||||
let className = "layout-editor-component";
|
||||
let className = classes.layoutEditorComponent;
|
||||
if (i === this.state.editor.selected_component) {
|
||||
className += " selected";
|
||||
className += " " + tableClasses.selected;
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
@@ -137,10 +140,10 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="layout-editor-outer">
|
||||
<div className="layout-editor-inner-container">
|
||||
<div className="layout-editor-inner">
|
||||
<div className="btn-group">
|
||||
<div className={classes.layoutEditorOuter}>
|
||||
<div className={classes.layoutEditorInnerContainer}>
|
||||
<div className={classes.layoutEditorInner}>
|
||||
<div className={classes.btnGroup}>
|
||||
<AddComponentButton
|
||||
allVariables={this.props.allVariables}
|
||||
addVariable={(v) => this.addVariable(v)}
|
||||
@@ -178,45 +181,45 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
<ArrowDown strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
<table className="layout-editor-component-list table">
|
||||
<tbody className="table-body">{components}</tbody>
|
||||
<table className={classes.layoutEditorComponentList}>
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
{components}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="tab-bar layout-editor-tabs">
|
||||
<div className={buttonGroupClasses.tabBar}>
|
||||
<button
|
||||
className={
|
||||
"toggle-left" +
|
||||
(!this.state.showComponentSettings
|
||||
? " button-pressed"
|
||||
: "")
|
||||
!this.state.showComponentSettings
|
||||
? buttonGroupClasses.pressed
|
||||
: ""
|
||||
}
|
||||
onClick={(_) => {
|
||||
onClick={(_) =>
|
||||
this.setState({
|
||||
showComponentSettings: false,
|
||||
});
|
||||
}}
|
||||
})
|
||||
}
|
||||
>
|
||||
Layout
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
"toggle-right" +
|
||||
(this.state.showComponentSettings
|
||||
? " button-pressed"
|
||||
: "")
|
||||
this.state.showComponentSettings
|
||||
? buttonGroupClasses.pressed
|
||||
: ""
|
||||
}
|
||||
onClick={(_) => {
|
||||
onClick={(_) =>
|
||||
this.setState({
|
||||
showComponentSettings: true,
|
||||
});
|
||||
}}
|
||||
})
|
||||
}
|
||||
>
|
||||
Component
|
||||
</button>
|
||||
</div>
|
||||
<div>{settings}</div>
|
||||
</div>
|
||||
<div className="layout-container">
|
||||
<div className={classes.layoutContainer}>
|
||||
<Layout
|
||||
getState={() => {
|
||||
this.props.commandSink.updateLayoutEditorLayoutState(
|
||||
@@ -244,12 +247,11 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>Layout Editor</h1>
|
||||
<hr />
|
||||
<div className="small">
|
||||
<div className={buttonGroupClasses.group}>
|
||||
<button
|
||||
className="toggle-left"
|
||||
onClick={(_) =>
|
||||
this.props.callbacks.closeLayoutEditor(true)
|
||||
}
|
||||
@@ -257,7 +259,6 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
<Check strokeWidth={2.5} /> OK
|
||||
</button>
|
||||
<button
|
||||
className="toggle-right"
|
||||
onClick={(_) =>
|
||||
this.props.callbacks.closeLayoutEditor(false)
|
||||
}
|
||||
@@ -265,7 +266,7 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
<X strokeWidth={2.5} /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -343,11 +344,11 @@ function AddComponentButton({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.TitleComponent)}
|
||||
>
|
||||
Title
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the name of the game and the category that is
|
||||
being run. Additionally, the game icon, the attempt
|
||||
count, and the total number of successfully finished
|
||||
@@ -355,11 +356,11 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.GraphComponent)}
|
||||
>
|
||||
Graph
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Visualizes how far the current run has been ahead or
|
||||
behind the chosen comparison throughout the whole
|
||||
run. All the individual deltas are shown as points
|
||||
@@ -367,11 +368,11 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.SplitsComponent)}
|
||||
>
|
||||
Splits
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
The main component for visualizing all the split
|
||||
times. Each segment is shown in a tabular fashion
|
||||
showing the segment icon, segment name, the delta
|
||||
@@ -382,13 +383,13 @@ function AddComponentButton({
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.DetailedTimerComponent)
|
||||
}
|
||||
>
|
||||
Detailed Timer
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows two timers, one for the total time of the
|
||||
current run and one showing the time of just the
|
||||
current segment. Other information, like segment
|
||||
@@ -397,11 +398,11 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.TimerComponent)}
|
||||
>
|
||||
Timer
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the total time of the current run as a digital
|
||||
clock. The color of the time shown is based on a how
|
||||
well the current run is doing compared to the chosen
|
||||
@@ -410,25 +411,25 @@ function AddComponentButton({
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.CurrentComparisonComponent)
|
||||
}
|
||||
>
|
||||
Current Comparison
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the name of the comparison that the timer is
|
||||
currently comparing against.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.CurrentPaceComponent)
|
||||
}
|
||||
>
|
||||
Current Pace
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows a prediction for the current run's final time.
|
||||
The remainder of the run is predicted based on the
|
||||
chosen comparison for the component. For example,
|
||||
@@ -438,23 +439,23 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.DeltaComponent)}
|
||||
>
|
||||
Delta
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows how far ahead or behind the current run is
|
||||
compared to the chosen comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.PbChanceComponent)
|
||||
}
|
||||
>
|
||||
PB Chance
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows how likely it is for the active run to beat
|
||||
the personal best. If there is no active run, it
|
||||
shows the general chance of beating the personal
|
||||
@@ -463,13 +464,13 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.PossibleTimeSaveComponent)
|
||||
}
|
||||
>
|
||||
Possible Time Save
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows how much time you can save on the current
|
||||
segment compared to the chosen comparison, based on
|
||||
the best segment time of the segment. This component
|
||||
@@ -478,13 +479,13 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.PreviousSegmentComponent)
|
||||
}
|
||||
>
|
||||
Previous Segment
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows how much time was saved or lost during the
|
||||
previous segment based on the chosen comparison.
|
||||
Additionally, the potential time save for the
|
||||
@@ -495,26 +496,26 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.SegmentTimeComponent)
|
||||
}
|
||||
>
|
||||
Segment Time
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the time for the current segment for the
|
||||
chosen comparison. If no comparison is specified it
|
||||
uses the timer's current comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.SumOfBestComponent)
|
||||
}
|
||||
>
|
||||
Sum of Best Segments
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the fastest possible time to complete a run of
|
||||
the current category, based on information collected
|
||||
from all the previous runs. This often matches up
|
||||
@@ -528,11 +529,11 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() => addComponent(LiveSplit.TextComponent)}
|
||||
>
|
||||
Text
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the text that you specify. This can either be
|
||||
a single centered text, or split up into a left and
|
||||
right text, which is suitable for a situation where
|
||||
@@ -542,13 +543,13 @@ function AddComponentButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.TotalPlaytimeComponent)
|
||||
}
|
||||
>
|
||||
Total Playtime
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Shows the total amount of time that the current
|
||||
category has been played for.
|
||||
</span>
|
||||
@@ -558,12 +559,14 @@ function AddComponentButton({
|
||||
Array.from(allVariables).map((name) => {
|
||||
return (
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
key={name}
|
||||
onClick={() => addVariable(name)}
|
||||
>
|
||||
{name}
|
||||
<span className="tooltip-text">
|
||||
<span
|
||||
className={tooltipClasses.tooltipText}
|
||||
>
|
||||
Creates a text component that shows the
|
||||
value of the custom variable "{name}".
|
||||
</span>
|
||||
@@ -572,26 +575,26 @@ function AddComponentButton({
|
||||
})}
|
||||
<Separator />
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.BlankSpaceComponent)
|
||||
}
|
||||
>
|
||||
Blank Space
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
An empty component that doesn't show anything other
|
||||
than a background. It mostly serves as padding
|
||||
between other components.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={() =>
|
||||
addComponent(LiveSplit.SeparatorComponent)
|
||||
}
|
||||
>
|
||||
Separator
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
A simple component that just renders a separator
|
||||
between components.
|
||||
</span>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { WebRenderer } from "../../livesplit-core/livesplit_core";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { LiveSplitServer } from "../../api/LiveSplitServer";
|
||||
import { Option } from "../../util/OptionUtil";
|
||||
import { LSOCommandSink } from "../LSOCommandSink";
|
||||
import { LSOCommandSink } from "../../util/LSOCommandSink";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Circle,
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
|
||||
import * as sidebarClasses from "../../css/Sidebar.module.scss";
|
||||
|
||||
export interface Props {
|
||||
isDesktop: boolean;
|
||||
layout: Layout;
|
||||
@@ -102,7 +104,7 @@ export class LayoutView extends React.Component<Props> {
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>Layout</h1>
|
||||
<hr />
|
||||
<button
|
||||
@@ -119,7 +121,7 @@ export class LayoutView extends React.Component<Props> {
|
||||
strokeWidth={0}
|
||||
size={12}
|
||||
fill="currentColor"
|
||||
className="modified-icon"
|
||||
className={sidebarClasses.modifiedIcon}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@@ -139,7 +141,7 @@ export class LayoutView extends React.Component<Props> {
|
||||
<button onClick={(_) => this.props.callbacks.openTimerView()}>
|
||||
<ArrowLeft strokeWidth={2.5} /> Back
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
} from "../../util/FrameRate";
|
||||
import { LiveSplitServer } from "../../api/LiveSplitServer";
|
||||
import { Option } from "../../util/OptionUtil";
|
||||
import { LSOCommandSink } from "../LSOCommandSink";
|
||||
import { LSOCommandSink } from "../../util/LSOCommandSink";
|
||||
import { Check, FlaskConical, X } from "lucide-react";
|
||||
|
||||
import "../../css/SettingsEditor.scss";
|
||||
import * as buttonGroupClasses from "../../css/ButtonGroup.module.scss";
|
||||
|
||||
export interface GeneralSettings {
|
||||
frameRate: FrameRateSetting;
|
||||
@@ -174,7 +174,7 @@ export class MainSettings extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-editor">
|
||||
<div>
|
||||
<h2>Hotkeys</h2>
|
||||
<SettingsComponent
|
||||
context="settings-editor-hotkeys"
|
||||
@@ -388,12 +388,11 @@ export class MainSettings extends React.Component<Props, State> {
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>Settings</h1>
|
||||
<hr />
|
||||
<div className="small">
|
||||
<div className={buttonGroupClasses.group}>
|
||||
<button
|
||||
className="toggle-left"
|
||||
onClick={(_) =>
|
||||
this.props.callbacks.closeMainSettings(
|
||||
true,
|
||||
@@ -404,7 +403,6 @@ export class MainSettings extends React.Component<Props, State> {
|
||||
<Check strokeWidth={2.5} /> OK
|
||||
</button>
|
||||
<button
|
||||
className="toggle-right"
|
||||
onClick={(_) =>
|
||||
this.props.callbacks.closeMainSettings(
|
||||
false,
|
||||
@@ -415,7 +413,7 @@ export class MainSettings extends React.Component<Props, State> {
|
||||
<X strokeWidth={2.5} /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+137
-121
@@ -44,7 +44,12 @@ import { corsBustingFetch } from "../../platform/CORS";
|
||||
import { ContextMenu, MenuItem, Position } from "../components/ContextMenu";
|
||||
import { Check, X } from "lucide-react";
|
||||
|
||||
import "../../css/RunEditor.scss";
|
||||
import * as classes from "../../css/RunEditor.module.scss";
|
||||
import * as buttonGroupClasses from "../../css/ButtonGroup.module.scss";
|
||||
import * as tableClasses from "../../css/Table.module.scss";
|
||||
import * as markdownClasses from "../../css/Markdown.module.scss";
|
||||
import * as tooltipClasses from "../../css/Tooltip.module.scss";
|
||||
import * as leaderboardClasses from "../../css/Leaderboard.module.scss";
|
||||
|
||||
export interface Props {
|
||||
editor: LiveSplit.RunEditor;
|
||||
@@ -164,8 +169,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
const { category, categoryNames } = this.getCurrentCategoriesInfo();
|
||||
|
||||
return (
|
||||
<div className="run-editor">
|
||||
<div className="run-editor-info">
|
||||
<>
|
||||
<div className={classes.runEditorInfo}>
|
||||
<GameIcon
|
||||
gameIcon={gameIcon}
|
||||
speedrunComIntegration={
|
||||
@@ -176,11 +181,10 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
downloadIcon={() => this.downloadIcon()}
|
||||
removeGameIcon={() => this.removeGameIcon()}
|
||||
/>
|
||||
<div className="run-editor-info-table">
|
||||
<div className="info-table-row">
|
||||
<div className="info-table-cell">
|
||||
<div className={classes.runEditorInfoTable}>
|
||||
<div className={classes.infoTableRow}>
|
||||
<div className={classes.infoTableCell}>
|
||||
<TextBox
|
||||
className="run-editor-game"
|
||||
value={this.state.editor.game}
|
||||
onChange={(e) => this.handleGameChange(e)}
|
||||
label="Game"
|
||||
@@ -190,9 +194,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="info-table-cell">
|
||||
<div className={classes.infoTableCell}>
|
||||
<TextBox
|
||||
className="run-editor-category"
|
||||
value={this.state.editor.category}
|
||||
onChange={(e) =>
|
||||
this.handleCategoryChange(e)
|
||||
@@ -205,10 +208,9 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-table-row">
|
||||
<div className="info-table-cell">
|
||||
<div className={classes.infoTableRow}>
|
||||
<div className={classes.infoTableCell}>
|
||||
<TextBox
|
||||
className="run-editor-offset"
|
||||
value={this.state.editor.offset}
|
||||
onChange={(e) => this.handleOffsetChange(e)}
|
||||
onBlur={(_) => this.handleOffsetBlur()}
|
||||
@@ -216,9 +218,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
label="Start Timer At"
|
||||
/>
|
||||
</div>
|
||||
<div className="info-table-cell">
|
||||
<div className={classes.infoTableCell}>
|
||||
<TextBox
|
||||
className="run-editor-attempts"
|
||||
value={this.state.editor.attempts}
|
||||
onChange={(e) =>
|
||||
this.handleAttemptsChange(e)
|
||||
@@ -231,18 +232,20 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bottom-section">
|
||||
<div className="side-buttons">
|
||||
{this.renderSideButtons(tab, category)}
|
||||
<div className={classes.bottomSection}>
|
||||
<div className={classes.sideButtonsOuter}>
|
||||
<div className={classes.sideButtonsInner}>
|
||||
{this.renderSideButtons(tab, category)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="editor-group">
|
||||
<div className="tab-bar">
|
||||
<div className={classes.editorGroup}>
|
||||
<div className={classes.tabBar}>
|
||||
{this.renderTabButtons(tab)}
|
||||
</div>
|
||||
{this.renderTab(tab, category)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,24 +256,18 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>Splits Editor</h1>
|
||||
<hr />
|
||||
<div className="small">
|
||||
<button
|
||||
className="toggle-left"
|
||||
onClick={(_) => this.close(true)}
|
||||
>
|
||||
<div className={buttonGroupClasses.group}>
|
||||
<button onClick={(_) => this.close(true)}>
|
||||
<Check strokeWidth={2.5} /> OK
|
||||
</button>
|
||||
<button
|
||||
className="toggle-right"
|
||||
onClick={(_) => this.close(false)}
|
||||
>
|
||||
<button onClick={(_) => this.close(false)}>
|
||||
<X strokeWidth={2.5} /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,19 +283,13 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
const visibleTabs = Object.values(Tab).filter((tab) =>
|
||||
this.shouldShowTab(tab as Tab),
|
||||
);
|
||||
return visibleTabs.map((tab, index) => {
|
||||
let toggleClassName = "toggle-middle";
|
||||
if (index === 0) {
|
||||
toggleClassName = "toggle-left";
|
||||
} else if (index === visibleTabs.length - 1) {
|
||||
toggleClassName = "toggle-right";
|
||||
}
|
||||
|
||||
const buttonClassName = currentTab === tab ? " button-pressed" : "";
|
||||
return visibleTabs.map((tab) => {
|
||||
const buttonClassName =
|
||||
currentTab === tab ? buttonGroupClasses.pressed : undefined;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={toggleClassName + buttonClassName}
|
||||
className={buttonClassName}
|
||||
onClick={(_) => this.switchTab(tab as Tab)}
|
||||
>
|
||||
{tabNames[tab as Tab]}
|
||||
@@ -309,7 +300,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
|
||||
private renderSegmentListButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">
|
||||
<>
|
||||
<button onClick={(_) => this.insertSegmentAbove()}>
|
||||
Insert Above
|
||||
</button>
|
||||
@@ -345,7 +336,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
clearTimes={() => this.clearTimes()}
|
||||
cleanSumOfBest={() => this.cleanSumOfBest()}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -366,19 +357,17 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private renderRulesButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">{this.renderAssociateRunButton()}</div>
|
||||
);
|
||||
return this.renderAssociateRunButton();
|
||||
}
|
||||
|
||||
private renderVariablesButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">
|
||||
<>
|
||||
{this.renderAssociateRunButton()}
|
||||
<button onClick={(_) => this.addCustomVariable()}>
|
||||
Add Variable
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -593,13 +582,13 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
subcategoryBoxes.push(
|
||||
<table className="table filter-table subcategory-table">
|
||||
<thead className="table-header">
|
||||
<table className={leaderboardClasses.subcategoryTable}>
|
||||
<thead className={classes.tableHeader}>
|
||||
<tr>
|
||||
<th>{variable.name}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-body">
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
{Object.values(variable.values.values).map(
|
||||
({ label }) => {
|
||||
const isSelected =
|
||||
@@ -609,7 +598,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
<td
|
||||
className={
|
||||
isSelected
|
||||
? "selected"
|
||||
? leaderboardClasses.selected
|
||||
: ""
|
||||
}
|
||||
onClick={(_) => {
|
||||
@@ -701,7 +690,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="btn-group">
|
||||
<>
|
||||
<button
|
||||
onClick={(_) => {
|
||||
if (category != null) {
|
||||
@@ -723,15 +712,17 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
: "Associate Run"}
|
||||
</button>
|
||||
{subcategoryBoxes}
|
||||
<table className="table filter-table">
|
||||
<thead className="table-header">
|
||||
<table className={leaderboardClasses.filterTable}>
|
||||
<thead className={classes.tableHeader}>
|
||||
<tr>
|
||||
<th>Filters</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-body">{filterList}</tbody>
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
{filterList}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -875,10 +866,10 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
fields.push(...customVariables);
|
||||
|
||||
return (
|
||||
<div className="run-editor-tab">
|
||||
<div className={classes.runEditorTab}>
|
||||
{fields.length === 0 && (
|
||||
<table className="table">
|
||||
<tbody className="table-body">
|
||||
<table className={tableClasses.table}>
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
<tr>
|
||||
<td>
|
||||
<p>
|
||||
@@ -1020,8 +1011,10 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
|
||||
return (
|
||||
<table className="table run-editor-tab leaderboard-table">
|
||||
<thead className="table-header">
|
||||
<table
|
||||
className={`${classes.runEditorTab} ${leaderboardClasses.leaderboardTable}`}
|
||||
>
|
||||
<thead className={classes.tableHeader}>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Player</th>
|
||||
@@ -1031,7 +1024,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-body">
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
{leaderboard.map((run) => {
|
||||
const platform = platformList.get(run.system.platform);
|
||||
if (isFiltered(platform, this.filters.platform)) {
|
||||
@@ -1098,8 +1091,12 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
valueName = map(value, (v) => v[1].label);
|
||||
}
|
||||
renderedVariables.push(
|
||||
<td className="variable-column">
|
||||
{valueName || ""}
|
||||
<td
|
||||
className={
|
||||
leaderboardClasses.variableColumn
|
||||
}
|
||||
>
|
||||
{valueName ?? ""}
|
||||
</td>,
|
||||
);
|
||||
}
|
||||
@@ -1119,8 +1116,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
const rowIndex = visibleRowCount;
|
||||
const evenOdd =
|
||||
rowIndex % 2 === 0
|
||||
? "table-row-odd"
|
||||
: "table-row-even";
|
||||
? tableClasses.explicitOdd
|
||||
: tableClasses.explicitEven;
|
||||
let expandedRow = null;
|
||||
|
||||
if (
|
||||
@@ -1143,7 +1140,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
expandedRow = (
|
||||
<tr
|
||||
key={`${run.id}_expanded`}
|
||||
className={`leaderboard-expanded-row ${evenOdd}`}
|
||||
className={`${leaderboardClasses.leaderboardExpandedRow} ${evenOdd}`}
|
||||
>
|
||||
<td
|
||||
colSpan={
|
||||
@@ -1152,14 +1149,20 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
>
|
||||
{embed}
|
||||
<div
|
||||
className="markdown"
|
||||
// FIXME: Move this into the
|
||||
// Markdown component, probably
|
||||
className={markdownClasses.markdown}
|
||||
style={{
|
||||
minHeight: 5,
|
||||
}}
|
||||
>
|
||||
<Markdown markdown={comment} />
|
||||
</div>
|
||||
<table className="run-meta-table">
|
||||
<table
|
||||
className={
|
||||
leaderboardClasses.runMetaTable
|
||||
}
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Date:</td>
|
||||
@@ -1206,7 +1209,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
<tr
|
||||
key={run.id}
|
||||
title={run.comment ?? ""}
|
||||
className={`leaderboard-row ${evenOdd}`}
|
||||
className={`${leaderboardClasses.leaderboardRow} ${evenOdd}`}
|
||||
onClick={(_) =>
|
||||
this.toggleExpandLeaderboardRow(rowIndex)
|
||||
}
|
||||
@@ -1214,7 +1217,9 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<td className="leaderboard-rank-column number">
|
||||
<td
|
||||
className={`${leaderboardClasses.leaderboardRankColumn} ${tableClasses.number}`}
|
||||
>
|
||||
{isUnique ? rank : "—"}
|
||||
</td>
|
||||
<td>
|
||||
@@ -1260,7 +1265,11 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
return [
|
||||
i !== 0 ? ", " : null,
|
||||
<span className="unregistered-user">
|
||||
<span
|
||||
className={
|
||||
leaderboardClasses.unregisteredUser
|
||||
}
|
||||
>
|
||||
{flag}
|
||||
{name}
|
||||
</span>,
|
||||
@@ -1268,7 +1277,9 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
})}
|
||||
</td>
|
||||
<td className="leaderboard-time-column number">
|
||||
<td
|
||||
className={`${leaderboardClasses.leaderboardTimeColumn} ${tableClasses.number}`}
|
||||
>
|
||||
<a
|
||||
href={run.weblink}
|
||||
target="_blank"
|
||||
@@ -1347,8 +1358,11 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="run-editor-tab run-editor-additional-info">
|
||||
<div className="run-editor-rules markdown">
|
||||
<div className={`${classes.runEditorTab} ${classes.rulesOuter}`}>
|
||||
<div
|
||||
// FIXME: Two divs?
|
||||
className={`${classes.rulesInner} ${markdownClasses.markdown}`}
|
||||
>
|
||||
{gameRules}
|
||||
{rules}
|
||||
{subcategoryRules}
|
||||
@@ -1359,8 +1373,10 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
|
||||
private renderSegmentsTable(): React.JSX.Element {
|
||||
return (
|
||||
<table className="table run-editor-tab run-editor-table">
|
||||
<thead className="table-header">
|
||||
<table
|
||||
className={`${classes.runEditorTab} ${classes.runEditorTable}`}
|
||||
>
|
||||
<thead className={classes.tableHeader}>
|
||||
<tr>
|
||||
<th>Icon</th>
|
||||
<th>Segment Name</th>
|
||||
@@ -1405,7 +1421,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-body">
|
||||
<tbody className={tableClasses.tableBody}>
|
||||
{this.state.editor.segments.map((s, segmentIndex) => {
|
||||
const segmentIcon =
|
||||
this.getSegmentIconUrl(segmentIndex);
|
||||
@@ -1416,7 +1432,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
className={
|
||||
s.selected === "Selected" ||
|
||||
s.selected === "Active"
|
||||
? "selected"
|
||||
? tableClasses.selected
|
||||
: ""
|
||||
}
|
||||
onClick={(e) =>
|
||||
@@ -1434,7 +1450,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
/>
|
||||
<td>
|
||||
<input
|
||||
className="name text-box"
|
||||
className={tableClasses.textBox}
|
||||
type="text"
|
||||
value={s.name}
|
||||
onFocus={(_) =>
|
||||
@@ -1447,7 +1463,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
className="number text-box"
|
||||
className={`${tableClasses.number} ${tableClasses.textBox}`}
|
||||
type="text"
|
||||
value={
|
||||
segmentIndex ===
|
||||
@@ -1476,8 +1492,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
.segmentTimeChanged) &&
|
||||
s.segment_time ===
|
||||
s.best_segment_time
|
||||
? "number text-box best-segment-time"
|
||||
: "number text-box"
|
||||
? `${tableClasses.number} ${tableClasses.textBox} ${classes.bestSegmentTime}`
|
||||
: `${tableClasses.number} ${tableClasses.textBox}`
|
||||
}
|
||||
type="text"
|
||||
value={
|
||||
@@ -1502,7 +1518,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
className="number text-box"
|
||||
className={`${tableClasses.number} ${tableClasses.textBox}`}
|
||||
type="text"
|
||||
value={
|
||||
segmentIndex ===
|
||||
@@ -1530,7 +1546,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
(comparisonTime, comparisonIndex) => (
|
||||
<td>
|
||||
<input
|
||||
className="number text-box"
|
||||
className={`${tableClasses.number} ${tableClasses.textBox}`}
|
||||
type="text"
|
||||
value={
|
||||
segmentIndex ===
|
||||
@@ -2464,11 +2480,11 @@ function GameIcon({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="game-icon-container"
|
||||
className={classes.gameIconContainer}
|
||||
onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}
|
||||
>
|
||||
{gameIcon !== undefined && (
|
||||
<img src={gameIcon} className="game-icon-image" />
|
||||
<img src={gameIcon} className={classes.gameIconImage} />
|
||||
)}
|
||||
</div>
|
||||
{position && (
|
||||
@@ -2477,11 +2493,11 @@ function GameIcon({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={changeGameIcon}
|
||||
>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Allows you to choose an image file to set as the
|
||||
game's icon. Certain file formats may not work
|
||||
everywhere.
|
||||
@@ -2490,22 +2506,22 @@ function GameIcon({
|
||||
{speedrunComIntegration && (
|
||||
<>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={downloadBoxArt}
|
||||
>
|
||||
Download Box Art
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Attempts to download the box art of the game
|
||||
from speedrun.com, to set as the game's
|
||||
icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={downloadIcon}
|
||||
>
|
||||
Download Icon
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Attempts to download the icon of the game
|
||||
from speedrun.com.
|
||||
</span>
|
||||
@@ -2514,11 +2530,11 @@ function GameIcon({
|
||||
)}
|
||||
{gameIcon !== undefined && (
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={removeGameIcon}
|
||||
>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Removes the icon of the game.
|
||||
</span>
|
||||
</MenuItem>
|
||||
@@ -2553,11 +2569,11 @@ function CleaningButton({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={clearHistory}
|
||||
>
|
||||
Clear Only History
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Splits store the entire history of all runs,
|
||||
including every segment time. This information is
|
||||
used by various components. You can clear the
|
||||
@@ -2567,22 +2583,22 @@ function CleaningButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={clearTimes}
|
||||
>
|
||||
Clear All Times
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
This removes all the times from the splits,
|
||||
including all the history, such that the splits are
|
||||
completely empty, as if they were just created.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={cleanSumOfBest}
|
||||
>
|
||||
Clean Sum of Best
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Allows you to interactively remove potential issues
|
||||
in the segment history that lead to an inaccurate
|
||||
Sum of Best. If you skip a split, whenever you will
|
||||
@@ -2625,31 +2641,31 @@ function ComparisonsButton({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={addComparison}
|
||||
>
|
||||
Add Comparison
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Adds a new custom comparison where you can store any
|
||||
times that you would like.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={importComparison}
|
||||
>
|
||||
Import Comparison
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Imports the Personal Best of a splits file you
|
||||
provide as a comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={generateGoalComparison}
|
||||
>
|
||||
Generate Goal Comparison
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Generates a custom goal comparison based on a goal
|
||||
time that you can specify. The comparison's times
|
||||
are automatically balanced based on the segment
|
||||
@@ -2666,11 +2682,11 @@ function ComparisonsButton({
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={copyComparison}
|
||||
>
|
||||
Copy Comparison
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Copies any existing comparison, including the
|
||||
Personal Best or even any other automatically
|
||||
provided comparison as a new custom comparison. You
|
||||
@@ -2698,7 +2714,7 @@ function SegmentIcon({
|
||||
|
||||
return (
|
||||
<td
|
||||
className="segment-icon-container"
|
||||
className={classes.segmentIconContainer}
|
||||
onClick={(e) => {
|
||||
if (position !== null) {
|
||||
return;
|
||||
@@ -2717,22 +2733,22 @@ function SegmentIcon({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={changeSegmentIcon}
|
||||
>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Allows you to choose an image file to set as the
|
||||
segment's icon. Certain file formats may not work
|
||||
everywhere.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={removeSegmentIcon}
|
||||
>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Removes the segment's icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
@@ -2789,31 +2805,31 @@ function CustomComparison({
|
||||
onClose={() => setPosition(null)}
|
||||
>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={renameComparison}
|
||||
>
|
||||
Rename
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Choose a new name for the custom comparison. There
|
||||
are reserved names that can't be used. You also
|
||||
can't have duplicate names.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={copyComparison}
|
||||
>
|
||||
Copy
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Creates a copy of the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="contextmenu-item tooltip"
|
||||
className={`${tooltipClasses.contextMenuItem} ${tooltipClasses.tooltip}`}
|
||||
onClick={removeComparison}
|
||||
>
|
||||
Remove
|
||||
<span className="tooltip-text">
|
||||
<span className={tooltipClasses.tooltipText}>
|
||||
Removes the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Option, bug, maybeDisposeAndThen } from "../../util/OptionUtil";
|
||||
import { DragUpload } from "../components/DragUpload";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { LSOCommandSink } from "../LSOCommandSink";
|
||||
import { LSOCommandSink } from "../../util/LSOCommandSink";
|
||||
import { showDialog } from "../components/Dialog";
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -34,7 +34,8 @@ import {
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
|
||||
import "../../css/SplitsSelection.scss";
|
||||
import * as classes from "../../css/SplitsSelection.module.scss";
|
||||
import * as sidebarClasses from "../../css/Sidebar.module.scss";
|
||||
|
||||
export interface EditingInfo {
|
||||
splitsKey?: number;
|
||||
@@ -86,14 +87,14 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
|
||||
if (this.state.splitsInfos == null) {
|
||||
content = (
|
||||
<div className="loading">
|
||||
<div className="loading-text">Loading...</div>
|
||||
<div className={classes.loading}>
|
||||
<div className={classes.loadingText}>Loading...</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<div className="splits-selection-container">
|
||||
<div className="main-actions">
|
||||
<div className={classes.splitsSelectionContainer}>
|
||||
<div className={classes.mainActions}>
|
||||
<button onClick={() => this.addNewSplits()}>
|
||||
<Plus strokeWidth={2.5} /> Add
|
||||
</button>
|
||||
@@ -102,12 +103,10 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
</button>
|
||||
</div>
|
||||
{this.state.splitsInfos?.length > 0 && (
|
||||
<div className="splits-table">
|
||||
<div className="splits-rows">
|
||||
{this.state.splitsInfos.map(([key, info]) =>
|
||||
this.renderSavedSplitsRow(key, info),
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.splitsTable}>
|
||||
{this.state.splitsInfos.map(([key, info]) =>
|
||||
this.renderSavedSplitsRow(key, info),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -115,7 +114,7 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
}
|
||||
return (
|
||||
<DragUpload importSplits={this.importSplitsFromFile.bind(this)}>
|
||||
<div className="splits-selection">{content}</div>
|
||||
{content}
|
||||
</DragUpload>
|
||||
);
|
||||
}
|
||||
@@ -129,14 +128,15 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
|
||||
private renderSavedSplitsRow(key: number, info: SplitsInfo) {
|
||||
const isOpened = key === this.props.openedSplitsKey;
|
||||
const classNames = [classes.splitsRow];
|
||||
if (isOpened) {
|
||||
classNames.push(classes.selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={isOpened ? "splits-row selected" : "splits-row"}
|
||||
key={key}
|
||||
>
|
||||
<div className={classNames.join(" ")} key={key}>
|
||||
{this.splitsTitle(info)}
|
||||
<div className="splits-row-buttons">
|
||||
<div className={classes.splitsRowButtons}>
|
||||
{isOpened ? null : (
|
||||
<>
|
||||
<button
|
||||
@@ -178,20 +178,18 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
|
||||
private splitsTitle(info: SplitsInfo) {
|
||||
return (
|
||||
<div className="splits-title-text">
|
||||
<div className="splits-text splits-game">
|
||||
<div className={classes.splitsTitleText}>
|
||||
<div className={`${classes.splitsText} ${classes.splitsGame}`}>
|
||||
{info.game || "Untitled"}
|
||||
</div>
|
||||
<div className="splits-text splits-category">
|
||||
{info.category || "—"}
|
||||
</div>
|
||||
<div className={classes.splitsText}>{info.category || "—"}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<>
|
||||
<h1>Splits</h1>
|
||||
<hr />
|
||||
<button
|
||||
@@ -220,7 +218,7 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
strokeWidth={0}
|
||||
size={12}
|
||||
fill="currentColor"
|
||||
className="modified-icon"
|
||||
className={sidebarClasses.modifiedIcon}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@@ -232,7 +230,7 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
<button onClick={(_) => this.props.callbacks.openTimerView()}>
|
||||
<ArrowLeft strokeWidth={2.5} /> Back
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+27
-23
@@ -16,7 +16,7 @@ import {
|
||||
MANUAL_GAME_TIME_MODE_SEGMENT_TIMES,
|
||||
} from "./MainSettings";
|
||||
import { LiveSplitServer } from "../../api/LiveSplitServer";
|
||||
import { LSOCommandSink } from "../LSOCommandSink";
|
||||
import { LSOCommandSink } from "../../util/LSOCommandSink";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
@@ -32,7 +32,9 @@ import {
|
||||
|
||||
import LiveSplitIcon from "../../assets/icon.svg";
|
||||
|
||||
import "../../css/TimerView.scss";
|
||||
import * as classes from "../../css/TimerView.module.scss";
|
||||
import * as sidebarClasses from "../../css/Sidebar.module.scss";
|
||||
import * as buttonGroupClasses from "../../css/ButtonGroup.module.scss";
|
||||
|
||||
export interface Props {
|
||||
isDesktop: boolean;
|
||||
@@ -155,10 +157,10 @@ export class TimerView extends React.Component<Props, State> {
|
||||
</div>
|
||||
{this.props.generalSettings.showControlButtons && (
|
||||
<div
|
||||
className="buttons"
|
||||
className={classes.buttons}
|
||||
style={{ width: this.props.layoutWidth }}
|
||||
>
|
||||
<div className="control-buttons">
|
||||
<div className={classes.controlButtons}>
|
||||
<button
|
||||
aria-label={
|
||||
this.props.currentPhase ===
|
||||
@@ -226,12 +228,12 @@ export class TimerView extends React.Component<Props, State> {
|
||||
)}
|
||||
{showManualGameTime && (
|
||||
<div
|
||||
className="buttons"
|
||||
className={classes.buttons}
|
||||
style={{ width: this.props.layoutWidth }}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="manual-game-time"
|
||||
className={classes.manualGameTime}
|
||||
value={this.state.manualGameTime}
|
||||
placeholder="Manual Game Time"
|
||||
onChange={(e) => {
|
||||
@@ -294,14 +296,16 @@ export class TimerView extends React.Component<Props, State> {
|
||||
|
||||
private renderSidebarContent() {
|
||||
return (
|
||||
<div className="sidebar-buttons">
|
||||
<div className="livesplit-title">
|
||||
<span className="livesplit-icon">
|
||||
<img src={LiveSplitIcon} alt="LiveSplit Logo" />
|
||||
</span>
|
||||
<>
|
||||
<div className={classes.liveSplitTitle}>
|
||||
<img
|
||||
className={classes.liveSplitIcon}
|
||||
src={LiveSplitIcon}
|
||||
alt="LiveSplit Logo"
|
||||
/>
|
||||
<h1>LiveSplit One</h1>
|
||||
</div>
|
||||
<hr className="livesplit-title-separator" />
|
||||
<hr />
|
||||
<button onClick={(_) => this.props.callbacks.openSplitsView()}>
|
||||
<List strokeWidth={2.5} />
|
||||
<span>
|
||||
@@ -311,7 +315,7 @@ export class TimerView extends React.Component<Props, State> {
|
||||
strokeWidth={0}
|
||||
size={12}
|
||||
fill="currentColor"
|
||||
className="modified-icon"
|
||||
className={sidebarClasses.modifiedIcon}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@@ -325,7 +329,7 @@ export class TimerView extends React.Component<Props, State> {
|
||||
strokeWidth={0}
|
||||
size={12}
|
||||
fill="currentColor"
|
||||
className="modified-icon"
|
||||
className={sidebarClasses.modifiedIcon}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
@@ -339,13 +343,13 @@ export class TimerView extends React.Component<Props, State> {
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="choose-comparison"
|
||||
className={classes.chooseComparison}
|
||||
>
|
||||
{this.props.allComparisons.map((comparison) => (
|
||||
<option>{comparison}</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="small">
|
||||
<div className={buttonGroupClasses.group}>
|
||||
<button
|
||||
onClick={(_) => {
|
||||
this.props.commandSink.setCurrentTimingMethod(
|
||||
@@ -353,10 +357,10 @@ export class TimerView extends React.Component<Props, State> {
|
||||
);
|
||||
}}
|
||||
className={
|
||||
(this.props.currentTimingMethod ===
|
||||
this.props.currentTimingMethod ===
|
||||
TimingMethod.RealTime
|
||||
? "button-pressed"
|
||||
: "") + " toggle-left"
|
||||
? buttonGroupClasses.pressed
|
||||
: ""
|
||||
}
|
||||
>
|
||||
Real Time
|
||||
@@ -368,10 +372,10 @@ export class TimerView extends React.Component<Props, State> {
|
||||
);
|
||||
}}
|
||||
className={
|
||||
(this.props.currentTimingMethod ===
|
||||
this.props.currentTimingMethod ===
|
||||
TimingMethod.GameTime
|
||||
? "button-pressed"
|
||||
: "") + " toggle-right"
|
||||
? buttonGroupClasses.pressed
|
||||
: ""
|
||||
}
|
||||
>
|
||||
Game Time
|
||||
@@ -384,7 +388,7 @@ export class TimerView extends React.Component<Props, State> {
|
||||
<button onClick={(_) => this.props.callbacks.openAboutView()}>
|
||||
<Info strokeWidth={2.5} /> About
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
isEvent,
|
||||
} from "../livesplit-core";
|
||||
import { WebCommandSink } from "../livesplit-core/livesplit_core";
|
||||
import { assert } from "../util/OptionUtil";
|
||||
import { showDialog } from "./components/Dialog";
|
||||
import { assert } from "./OptionUtil";
|
||||
import { showDialog } from "../ui/components/Dialog";
|
||||
|
||||
interface Callbacks {
|
||||
handleEvent(event: Event): void;
|
||||
@@ -2,6 +2,8 @@ import * as React from "react";
|
||||
import markdownit from "markdown-it";
|
||||
import { emoteList } from "../api/EmoteList";
|
||||
|
||||
import * as classes from "../css/Markdown.module.scss";
|
||||
|
||||
const UNSAFE = markdownit({ html: true, breaks: false, linkify: true });
|
||||
const SAFE = markdownit({ html: false, breaks: true, linkify: true });
|
||||
|
||||
@@ -40,7 +42,7 @@ function replaceTwitchEmotes(text: string): string {
|
||||
export function replaceFlag(countryCode: string): React.JSX.Element {
|
||||
const url = `https://www.speedrun.com/images/flags/${countryCode}.png`;
|
||||
|
||||
return <img className="flag" src={url} alt={countryCode} />;
|
||||
return <img className={classes.flag} src={url} alt={countryCode} />;
|
||||
}
|
||||
|
||||
export const Markdown = React.memo(renderMarkdown);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as React from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import * as toastClasses from "../css/Toast.module.scss";
|
||||
|
||||
export type Option<T> = T | null | undefined;
|
||||
|
||||
export function expect<T>(obj: Option<T>, message: string): T {
|
||||
@@ -21,7 +23,7 @@ export function panic(message: string): never {
|
||||
|
||||
export function bug(message: string): void {
|
||||
toast.error(
|
||||
<>
|
||||
<div>
|
||||
<b>You encountered a bug:</b>
|
||||
<p>
|
||||
<i>{message}</i>
|
||||
@@ -31,10 +33,10 @@ export function bug(message: string): void {
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</>,
|
||||
</div>,
|
||||
{
|
||||
autoClose: false,
|
||||
className: "toast-class toast-bug",
|
||||
className: `${toastClasses.toastClass} ${toastClasses.toastBug}`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+146
-87
@@ -1,18 +1,21 @@
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import HtmlInlineScriptPlugin from 'html-inline-script-webpack-plugin';
|
||||
import HtmlInlineScriptPlugin from "html-inline-script-webpack-plugin";
|
||||
import FaviconsWebpackPlugin from "favicons-webpack-plugin";
|
||||
import { CleanWebpackPlugin } from "clean-webpack-plugin";
|
||||
import WorkboxPlugin from "workbox-webpack-plugin";
|
||||
import ReactRefreshTypeScript from 'react-refresh-typescript';
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import ReactRefreshTypeScript from "react-refresh-typescript";
|
||||
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
|
||||
import webpack from "webpack";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { defineReactCompilerLoaderOption, reactCompilerLoader } from "react-compiler-webpack";
|
||||
import { fileURLToPath } from "url";
|
||||
import {
|
||||
defineReactCompilerLoaderOption,
|
||||
reactCompilerLoader,
|
||||
} from "react-compiler-webpack";
|
||||
|
||||
function parseChangelog() {
|
||||
return execSync("git log --grep \"^Changelog: \" -10")
|
||||
return execSync('git log --grep "^Changelog: " -10')
|
||||
.toString()
|
||||
.split(/^commit /m)
|
||||
.slice(1)
|
||||
@@ -26,7 +29,7 @@ function parseChangelog() {
|
||||
throw `Date not found in commit:\n${commit}`;
|
||||
}
|
||||
const dateValue = new Date(dateString);
|
||||
const date = dateValue.toISOString().split('T')[0];
|
||||
const date = dateValue.toISOString().split("T")[0];
|
||||
const id = commit.substring(0, commit.indexOf("\n"));
|
||||
const message = commit
|
||||
.substring(changelogIndex + 15)
|
||||
@@ -43,20 +46,28 @@ function parseChangelog() {
|
||||
|
||||
export default async (env, argv) => {
|
||||
const getContributorsForRepo = async (repoName) => {
|
||||
const contributorsData = await fetch(`https://api.github.com/repos/LiveSplit/${repoName}/contributors`, {
|
||||
headers: {
|
||||
"Authorization": env.GITHUB_TOKEN ? `Bearer ${env.GITHUB_TOKEN}` : undefined,
|
||||
const contributorsData = await fetch(
|
||||
`https://api.github.com/repos/LiveSplit/${repoName}/contributors`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: env.GITHUB_TOKEN
|
||||
? `Bearer ${env.GITHUB_TOKEN}`
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
return contributorsData.json();
|
||||
}
|
||||
};
|
||||
|
||||
const lsoContributorsList = await getContributorsForRepo("LiveSplitOne");
|
||||
const coreContributorsList = await getContributorsForRepo("livesplit-core");
|
||||
|
||||
const coreContributorsMap = {};
|
||||
for (const coreContributor of coreContributorsList) {
|
||||
if (coreContributor.type === "User" && !coreContributor.login.includes("dependabot")) {
|
||||
if (
|
||||
coreContributor.type === "User" &&
|
||||
!coreContributor.login.includes("dependabot")
|
||||
) {
|
||||
coreContributorsMap[coreContributor.login] = coreContributor;
|
||||
}
|
||||
}
|
||||
@@ -65,7 +76,10 @@ export default async (env, argv) => {
|
||||
const existingContributor = coreContributorsMap[lsoContributor.login];
|
||||
if (existingContributor) {
|
||||
existingContributor.contributions += lsoContributor.contributions;
|
||||
} else if (lsoContributor.type === "User" && !lsoContributor.login.includes("dependabot")) {
|
||||
} else if (
|
||||
lsoContributor.type === "User" &&
|
||||
!lsoContributor.login.includes("dependabot")
|
||||
) {
|
||||
coreContributorsMap[lsoContributor.login] = lsoContributor;
|
||||
}
|
||||
}
|
||||
@@ -73,13 +87,16 @@ export default async (env, argv) => {
|
||||
const contributorsList = Object.values(coreContributorsMap)
|
||||
// Sort by contributions, but fallback to alphabetical order for the
|
||||
// same amount of contributions
|
||||
.sort((a, b) => a.login > b.login ? 1 : b.login > a.login ? -1 : 0)
|
||||
.sort((a, b) => (a.login > b.login ? 1 : b.login > a.login ? -1 : 0))
|
||||
.sort((a, b) => b.contributions - a.contributions)
|
||||
.map((user) => {
|
||||
return { id: user.id, name: user.login };
|
||||
});
|
||||
const commitHash = execSync("git rev-parse --short HEAD").toString();
|
||||
const date = new Date().toISOString().replace("T", " ").replace(/\..+/, " UTC");
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.replace(/\..+/, " UTC");
|
||||
|
||||
const changelog = parseChangelog();
|
||||
|
||||
@@ -87,63 +104,82 @@ export default async (env, argv) => {
|
||||
|
||||
const isProduction = argv.mode === "production";
|
||||
const isTauri = env.TAURI === "true";
|
||||
const distPath = path.join(...[
|
||||
basePath,
|
||||
...(isTauri ? ["src-tauri", "target", "dist"] : ["dist"]),
|
||||
]);
|
||||
const distPath = path.join(
|
||||
...[
|
||||
basePath,
|
||||
...(isTauri ? ["src-tauri", "target", "dist"] : ["dist"]),
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
entry: {
|
||||
"bundle": ["./src/index.tsx"],
|
||||
bundle: ["./src/index.tsx"],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
path: distPath,
|
||||
publicPath: '',
|
||||
publicPath: "",
|
||||
},
|
||||
|
||||
devtool: isProduction ? undefined : "source-map",
|
||||
|
||||
devServer: {
|
||||
port: 8080,
|
||||
hot: true
|
||||
hot: true,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".json", ".wasm"],
|
||||
extensions: [
|
||||
".webpack.js",
|
||||
".web.js",
|
||||
".ts",
|
||||
".tsx",
|
||||
".js",
|
||||
".json",
|
||||
".wasm",
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
...(isProduction ? [new CleanWebpackPlugin({
|
||||
protectWebpackAssets: false,
|
||||
cleanAfterEveryBuildPatterns: ['*.LICENSE.txt'],
|
||||
})] : []),
|
||||
...(isTauri ? [] : [new FaviconsWebpackPlugin({
|
||||
logo: path.resolve("src/assets/icon.svg"),
|
||||
inject: true,
|
||||
logoMaskable: path.resolve("src/assets/maskable.svg"),
|
||||
favicons: {
|
||||
appName: "LiveSplit One",
|
||||
appDescription: "A version of LiveSplit that works on a lot of platforms.",
|
||||
developerName: "CryZe",
|
||||
developerURL: "https://livesplit.org",
|
||||
background: "#171717",
|
||||
theme_color: "#232323",
|
||||
appleStatusBarStyle: "black-translucent",
|
||||
icons: {
|
||||
appleIcon: {
|
||||
offset: 10,
|
||||
},
|
||||
appleStartup: {
|
||||
offset: 15,
|
||||
},
|
||||
windows: false,
|
||||
coast: false,
|
||||
yandex: false,
|
||||
},
|
||||
start_url: "/",
|
||||
},
|
||||
})]),
|
||||
...(isProduction
|
||||
? [
|
||||
new CleanWebpackPlugin({
|
||||
protectWebpackAssets: false,
|
||||
cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(isTauri
|
||||
? []
|
||||
: [
|
||||
new FaviconsWebpackPlugin({
|
||||
logo: path.resolve("src/assets/icon.svg"),
|
||||
inject: true,
|
||||
logoMaskable: path.resolve("src/assets/maskable.svg"),
|
||||
favicons: {
|
||||
appName: "LiveSplit One",
|
||||
appDescription:
|
||||
"A version of LiveSplit that works on a lot of platforms.",
|
||||
developerName: "CryZe",
|
||||
developerURL: "https://livesplit.org",
|
||||
background: "#171717",
|
||||
theme_color: "#232323",
|
||||
appleStatusBarStyle: "black-translucent",
|
||||
icons: {
|
||||
appleIcon: {
|
||||
offset: 10,
|
||||
},
|
||||
appleStartup: {
|
||||
offset: 15,
|
||||
},
|
||||
windows: false,
|
||||
coast: false,
|
||||
yandex: false,
|
||||
},
|
||||
start_url: "/",
|
||||
},
|
||||
}),
|
||||
]),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./src/index.html",
|
||||
}),
|
||||
@@ -153,28 +189,39 @@ export default async (env, argv) => {
|
||||
CONTRIBUTORS_LIST: JSON.stringify(contributorsList),
|
||||
CHANGELOG: JSON.stringify(changelog),
|
||||
}),
|
||||
...(isProduction ? [
|
||||
new HtmlInlineScriptPlugin({
|
||||
scriptMatchPattern: ['^bundle.js$'],
|
||||
}),
|
||||
...(isTauri ? [] : [new WorkboxPlugin.GenerateSW({
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
|
||||
exclude: [
|
||||
/^assets/,
|
||||
/\.LICENSE\.txt$/,
|
||||
],
|
||||
...(isProduction
|
||||
? [
|
||||
new HtmlInlineScriptPlugin({
|
||||
scriptMatchPattern: ["^bundle.js$"],
|
||||
}),
|
||||
...(isTauri
|
||||
? []
|
||||
: [
|
||||
new WorkboxPlugin.GenerateSW({
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
maximumFileSizeToCacheInBytes:
|
||||
100 * 1024 * 1024,
|
||||
exclude: [/^assets/, /\.LICENSE\.txt$/],
|
||||
|
||||
runtimeCaching: [{
|
||||
urlPattern: (context) => {
|
||||
return self.origin === context.url.origin &&
|
||||
context.url.pathname.startsWith("/assets/");
|
||||
},
|
||||
handler: "CacheFirst",
|
||||
}],
|
||||
})])
|
||||
] : []),
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: (context) => {
|
||||
return (
|
||||
self.origin ===
|
||||
context.url.origin &&
|
||||
context.url.pathname.startsWith(
|
||||
"/assets/",
|
||||
)
|
||||
);
|
||||
},
|
||||
handler: "CacheFirst",
|
||||
},
|
||||
],
|
||||
}),
|
||||
]),
|
||||
]
|
||||
: []),
|
||||
...(!isProduction ? [new ReactRefreshWebpackPlugin()] : []),
|
||||
],
|
||||
|
||||
@@ -183,21 +230,28 @@ export default async (env, argv) => {
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
...(isProduction ? [{
|
||||
loader: reactCompilerLoader,
|
||||
options: defineReactCompilerLoaderOption({
|
||||
babelTransFormOpt: {
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-explicit-resource-management",
|
||||
],
|
||||
},
|
||||
}),
|
||||
}] : []),
|
||||
...(isProduction
|
||||
? [
|
||||
{
|
||||
loader: reactCompilerLoader,
|
||||
options: defineReactCompilerLoaderOption({
|
||||
babelTransFormOpt: {
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-explicit-resource-management",
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
getCustomTransformers: () => ({
|
||||
before: [!isProduction && ReactRefreshTypeScript()].filter(Boolean),
|
||||
before: [
|
||||
!isProduction &&
|
||||
ReactRefreshTypeScript(),
|
||||
].filter(Boolean),
|
||||
}),
|
||||
transpileOnly: !isProduction,
|
||||
},
|
||||
@@ -215,6 +269,11 @@ export default async (env, argv) => {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
auto: true,
|
||||
// In development mode, we want to know
|
||||
// which classes are being used in the DOM.
|
||||
localIdentName: isProduction
|
||||
? "[hash:base64]"
|
||||
: "[name]_[local]_[hash:base64]",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -223,7 +282,7 @@ export default async (env, argv) => {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|woff|ico|svg)$/,
|
||||
type: 'asset/resource'
|
||||
type: "asset/resource",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user