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:
Christopher Serr
2025-03-31 18:42:11 +00:00
committed by GitHub
parent 42b3c12890
commit f03689bc81
60 changed files with 1657 additions and 1601 deletions
+1 -1
View File
@@ -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";
+5 -9
View File
@@ -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;
}
}
+45
View File
@@ -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;
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ dialog {
font-size: larger;
}
>* {
> * {
margin: 0;
}
-20
View File
@@ -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);
}
-2
View File
@@ -1,5 +1,3 @@
@use "Font";
.resizableLayout {
position: absolute;
top: 0;
+69
View File
@@ -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;
}
}
-77
View File
@@ -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;
}
}
}
+106
View File
@@ -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;
}
}
+33
View File
@@ -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}
);
}
}
-36
View File
@@ -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;
}
+267
View File
@@ -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;
}
-384
View File
@@ -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});
}
}
}
-16
View File
@@ -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%;
}
}
}
}
+50
View File
@@ -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;
}
-105
View File
@@ -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;
}
}
+115
View File
@@ -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;
}
}
-118
View File
@@ -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;
}
}
}
}
+163
View File
@@ -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%;
}
}
-176
View File
@@ -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%;
}
}
}
+82
View File
@@ -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;
}
-46
View File
@@ -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;
}
}
+28
View File
@@ -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;
}
}
-28
View File
@@ -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;
}
}
+45
View File
@@ -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;
}
}
-45
View File
@@ -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;
}
-17
View File
@@ -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
View File
@@ -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;
}
}
-5
View File
@@ -1,5 +0,0 @@
@mixin mobile {
.is-mobile & {
@content;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
<body>
<div id="base">
<div class="initial-load">
<div class="initial-load-text">Loading...</div>
Loading...
</div>
</div>
</body>
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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) =>
+6 -2
View File
@@ -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) => {
+6 -4
View File
@@ -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 -1
View File
@@ -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) =>
+10 -2
View File
@@ -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>
);
}
+47 -9
View File
@@ -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}
>
{(() => {
+26 -12
View File
@@ -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) =>
+9 -1
View File
@@ -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>
);
}
+19 -14
View File
@@ -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>
);
}
+4 -4
View File
@@ -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>
</>
);
}
+70 -67
View File
@@ -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>
+6 -4
View File
@@ -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>
</>
);
}
}
+6 -8
View File
@@ -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
View File
@@ -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>
+24 -26
View File
@@ -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
View File
@@ -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;
+3 -1
View File
@@ -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);
+5 -3
View File
@@ -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
View File
@@ -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",
},
],
},