This commit is contained in:
Infinite 2020-01-26 15:08:53 +01:00
parent c2544fee98
commit 3fa3f61d93
18 changed files with 422 additions and 189 deletions

View file

@ -31,8 +31,6 @@ module.exports = merge(commonConfig, {
compress: true,
host: '0.0.0.0',
hot: true, // enable HMR on the server host: '0.0.0.0',
transportMode: 'ws',
injectClient: false,
port: process.env.PORT,
historyApiFallback: true,
stats: {

View file

@ -1,7 +1,7 @@
import React from "react";
import { Spinner } from "react-bootstrap";
import { useParams } from "react-router-dom";
import { StashService } from "src/core/StashService";
import { LoadingIndicator } from 'src/components/Shared';
import { GalleryViewer } from "./GalleryViewer";
export const Gallery: React.FC = () => {
@ -11,7 +11,7 @@ export const Gallery: React.FC = () => {
const gallery = data?.findGallery;
if (loading || !gallery)
return <Spinner animation="border" variant="light" />;
return <LoadingIndicator />;
if (error) return <div>{error.message}</div>;
return (

View file

@ -67,7 +67,7 @@ export const MainNavbar: React.FC = () => {
<Navbar fixed="top" variant="dark" bg="dark">
<Navbar.Brand as="div">
<Link to="/">
<Button variant="secondary">Stash</Button>
<Button className="minimal">Stash</Button>
</Link>
</Navbar.Brand>
<Nav className="mr-auto">
@ -78,7 +78,7 @@ export const MainNavbar: React.FC = () => {
to={i.href}
key={i.href}
>
<Button variant="secondary">
<Button className="minimal">
<Icon icon={i.icon} />
{i.text}
</Button>
@ -88,7 +88,7 @@ export const MainNavbar: React.FC = () => {
<Nav>
{newButton}
<LinkContainer exact to="/settings">
<Button variant="secondary">
<Button className="minimal">
<Icon icon="cog" />
</Button>
</LinkContainer>

View file

@ -17,11 +17,13 @@ interface ITypeProps {
type?: "performers" | "studios" | "tags";
}
interface IFilterProps {
initialIds: string[];
ids?: string[];
initialIds?: string[];
onSelect: (item: ValidTypes[]) => void;
noSelectionString?: string;
className?: string;
isMulti?: boolean;
isClearable?: boolean;
}
interface ISelectProps {
className?: string;
@ -31,8 +33,9 @@ interface ISelectProps {
onCreateOption?: (value: string) => void;
isLoading: boolean;
onChange: (item: ValueType<Option>) => void;
initialIds: string[];
initialIds?: string[];
isMulti?: boolean;
isClearable?: boolean,
onInputChange?: (input: string) => void;
placeholder?: string;
}
@ -48,9 +51,10 @@ interface ISceneGallerySelect {
}
const getSelectedValues = (selectedItems: ValueType<Option>) =>
selectedItems ?
(Array.isArray(selectedItems) ? selectedItems : [selectedItems]).map(
item => item.value
);
) : [];
export const SceneGallerySelect: React.FC<ISceneGallerySelect> = props => {
const { data, loading } = StashService.useValidGalleriesForScene(
@ -165,6 +169,8 @@ export const PerformerSelect: React.FC<IFilterProps> = props => {
label: item.name ?? ""
}));
const placeholder = props.noSelectionString ?? "Select performer...";
const selectedOptions:Option[] = props.ids ?
items.filter(item => props.ids?.indexOf(item.value) !== -1) : [];
const onChange = (selectedItems: ValueType<Option>) => {
const selectedIds = getSelectedValues(selectedItems);
@ -176,6 +182,7 @@ export const PerformerSelect: React.FC<IFilterProps> = props => {
return (
<SelectComponent
{...props}
selectedOptions={selectedOptions}
onChange={onChange}
type="performers"
isLoading={loading}
@ -194,6 +201,8 @@ export const StudioSelect: React.FC<IFilterProps> = props => {
label: item.name
}));
const placeholder = props.noSelectionString ?? "Select studio...";
const selectedOptions:Option[] = props.ids ?
items.filter(item => props.ids?.indexOf(item.value) !== -1) : [];
const onChange = (selectedItems: ValueType<Option>) => {
const selectedIds = getSelectedValues(selectedItems);
@ -210,13 +219,14 @@ export const StudioSelect: React.FC<IFilterProps> = props => {
isLoading={loading}
items={items}
placeholder={placeholder}
selectedOptions={selectedOptions}
/>
);
};
export const TagSelect: React.FC<IFilterProps> = props => {
const [loading, setLoading] = useState(false);
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [selectedIds, setSelectedIds] = useState<string[]>(props.ids ?? []);
const { data, loading: dataLoading } = StashService.useAllTagsForFilter();
const [createTag] = StashService.useTagCreate({ name: "" });
const Toast = useToast();
@ -290,6 +300,7 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
selectedOptions,
isLoading,
onCreateOption,
isClearable = true,
creatable = false,
isMulti = false,
onInputChange,
@ -298,26 +309,58 @@ const SelectComponent: React.FC<ISelectProps & ITypeProps> = ({
const defaultValue =
items.filter(item => initialIds?.indexOf(item.value) !== -1) ?? null;
const styles = {
control: (provided:any) => ({
...provided,
background: '#394b59',
borderColor: 'rgba(16,22,26,.4)'
}),
singleValue: (provided:any) => ({
...provided,
color: 'f5f8fa',
}),
placeholder: (provided:any) => ({
...provided,
color: 'f5f8fa',
}),
menu: (provided:any) => ({
...provided,
color: 'f5f8fa',
background: '#394b59',
borderColor: 'rgba(16,22,26,.4)',
zIndex: 3
}),
option: (provided:any, state:any ) => (
state.isFocused ? { ...provided, backgroundColor: '#137cbd' } : provided
),
multiValueRemove: (provided:any, state:any) => (
{ ...provided, color: 'black' }
)
};
const props = {
className,
options: items,
value: selectedOptions,
styles,
className,
onChange,
isMulti,
isClearable,
defaultValue,
noOptionsMessage: () => (type !== "tags" ? "None" : null),
placeholder,
onInputChange
onInputChange,
isLoading,
components: { IndicatorSeparator: () => null }
};
return creatable ? (
<CreatableSelect
{...props}
isLoading={isLoading}
isDisabled={isLoading}
onCreateOption={onCreateOption}
/>
) : (
<Select {...props} isLoading={isLoading} />
<Select {...props} />
);
};

View file

@ -39,8 +39,6 @@
.scene-wall-item-container {
display: flex;
justify-content: center;
// align-items: center;
// overflow: hidden; // Commented out since it shows gaps in the wall
position: relative;
width: 100%;
height: 100%;
@ -62,6 +60,7 @@
padding: 5px;
width: 100%;
bottom: 0;
left: 0;
background: linear-gradient(rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.65));
overflow: hidden;
text-align: center;
@ -80,18 +79,17 @@
left: -5px;
right: -5px;
bottom: -5px;
/*background-color: rgba(255, 255, 255, 0.75);*/
/*backdrop-filter: blur(5px);*/
z-index: -1;
}
.wall.grid-item video, .wall.grid-item img {
.wall-item video, .wall-item img {
width: 100%;
height: 100%;
object-fit: contain;
}
.wall.grid-item {
.wall-item {
width: 20%;
padding: 0 !important;
line-height: 0;
overflow: visible;

View file

@ -1,5 +1,5 @@
import _ from "lodash";
import React, { FunctionComponent, useRef, useState, useEffect } from "react";
import React, { useRef, useState, useEffect } from "react";
import { Link } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import { StashService } from "src/core/StashService";
@ -16,10 +16,10 @@ interface IWallItemProps {
) => void;
}
export const WallItem: FunctionComponent<IWallItemProps> = (
export const WallItem: React.FC<IWallItemProps> = (
props: IWallItemProps
) => {
const [videoPath, setVideoPath] = useState<string | undefined>(undefined);
const [videoPath, setVideoPath] = useState<string>();
const [previewPath, setPreviewPath] = useState<string>("");
const [screenshotPath, setScreenshotPath] = useState<string>("");
const [title, setTitle] = useState<string>("");
@ -28,10 +28,7 @@ export const WallItem: FunctionComponent<IWallItemProps> = (
const videoHoverHook = VideoHoverHook.useVideoHover({
resetOnMouseLeave: true
});
const showTextContainer =
!!config.data && !!config.data.configuration
? config.data.configuration.interface.wallShowTitle
: true;
const showTextContainer = config.data?.configuration.interface.wallShowTitle ?? true;
function onMouseEnter() {
VideoHoverHook.onMouseEnter(videoHoverHook);
@ -122,7 +119,7 @@ export const WallItem: FunctionComponent<IWallItemProps> = (
style.transformOrigin = props.origin;
}
return (
<div className="wall grid-item">
<div className="wall-item">
<div
className={className.join(" ")}
style={style}

View file

@ -1,4 +1,4 @@
import React, { FunctionComponent, useState } from "react";
import React, { useState } from "react";
import * as GQL from "src/core/generated-graphql";
import { WallItem } from "./WallItem";
import "./Wall.scss";
@ -11,7 +11,7 @@ interface IWallPanelProps {
) => void;
}
export const WallPanel: FunctionComponent<IWallPanelProps> = (
export const WallPanel: React.FC<IWallPanelProps> = (
props: IWallPanelProps
) => {
const [showOverlay, setShowOverlay] = useState<boolean>(false);

View file

@ -229,7 +229,7 @@ export const AddFilter: React.FC<IAddFilterProps> = (
placement="top"
overlay={<Tooltip id="filter-tooltip">Filter</Tooltip>}
>
<Button onClick={() => onToggle()} active={isOpen}>
<Button variant="secondary" onClick={() => onToggle()} active={isOpen}>
<Icon icon="filter" />
</Button>
</OverlayTrigger>

View file

@ -139,6 +139,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
}
>
<Button
variant="secondary"
key={option}
active={props.filter.displayMode === option}
onClick={() => onChangeDisplayMode(option)}
@ -157,7 +158,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
onClick={() => onClickCriterionTag(criterion)}
>
{criterion.getLabel()}
<Button onClick={() => onRemoveCriterionTag(criterion)}>
<Button variant="secondary" onClick={() => onRemoveCriterionTag(criterion)}>
<Icon icon="times" />
</Button>
</Badge>
@ -226,16 +227,15 @@ export const ListFilter: React.FC<IListFilterProps> = (
function maybeRenderZoom() {
if (props.onChangeZoom) {
return (
<span className="zoom-slider">
<Form.Control
type="range"
min={0}
max={3}
onChange={(event: any) =>
onChangeZoom(Number.parseInt(event.target.value, 10))
}
/>
</span>
<Form.Control
className="zoom-slider"
type="range"
min={0}
max={3}
onChange={(event: any) =>
onChangeZoom(Number.parseInt(event.target.value, 10))
}
/>
);
}
}
@ -243,7 +243,7 @@ export const ListFilter: React.FC<IListFilterProps> = (
function render() {
return (
<>
<div className="d-flex justify-content-center m-auto">
<div className="filter-container">
<Form.Control
placeholder="Search..."
value={props.filter.searchTerm}
@ -262,32 +262,32 @@ export const ListFilter: React.FC<IListFilterProps> = (
))}
</Form.Control>
<ButtonGroup className="filter-item">
<Dropdown>
<Dropdown.Toggle variant="secondary" id="more-menu">
<Dropdown as={ButtonGroup}>
<Dropdown.Toggle split variant="secondary" id="more-menu">
{props.filter.sortBy}
</Dropdown.Toggle>
<Dropdown.Menu>{renderSortByOptions()}</Dropdown.Menu>
<OverlayTrigger
overlay={
<Tooltip id="sort-direction-tooltip">
{props.filter.sortDirection === "asc"
? "Ascending"
: "Descending"}
</Tooltip>
}
>
<Button variant="secondary" onClick={onChangeSortDirection}>
<Icon
icon={
props.filter.sortDirection === "asc"
? "caret-up"
: "caret-down"
}
/>
</Button>
</OverlayTrigger>
</Dropdown>
<OverlayTrigger
overlay={
<Tooltip id="sort-direction-tooltip">
{props.filter.sortDirection === "asc"
? "Ascending"
: "Descending"}
</Tooltip>
}
>
<Button onClick={onChangeSortDirection}>
<Icon
icon={
props.filter.sortDirection === "asc"
? "caret-up"
: "caret-down"
}
/>
</Button>
</OverlayTrigger>
</ButtonGroup>
<AddFilter

View file

@ -39,6 +39,7 @@ export const Pagination: React.FC<IPaginationProps> = ({
const pageButtons = pages.map((page: number) => (
<Button
variant="secondary"
key={page}
active={currentPage === page}
onClick={() => onChangePage(page)}
@ -48,11 +49,12 @@ export const Pagination: React.FC<IPaginationProps> = ({
));
return (
<ButtonGroup className="filter-container">
<Button disabled={currentPage === 1} onClick={() => onChangePage(1)}>
<ButtonGroup className="filter-container pagination">
<Button variant="secondary" disabled={currentPage === 1} onClick={() => onChangePage(1)}>
First
</Button>
<Button
variant="secondary"
disabled={currentPage === 1}
onClick={() => onChangePage(currentPage - 1)}
>
@ -60,12 +62,14 @@ export const Pagination: React.FC<IPaginationProps> = ({
</Button>
{pageButtons}
<Button
variant="secondary"
disabled={currentPage === totalPages}
onClick={() => onChangePage(currentPage + 1)}
>
Next
</Button>
<Button
variant="secondary"
disabled={currentPage === totalPages}
onClick={() => onChangePage(totalPages)}
>

View file

@ -0,0 +1,15 @@
.pagination {
.btn {
flex-grow: 0;
padding-left: 15px;
padding-right: 15px;
border-left: 1px solid $body-bg;
border-right: 1px solid $body-bg;
transition: none;
}
}
.zoom-slider {
padding-left: 0;
padding-right: 0;
}

View file

@ -91,7 +91,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
return (
<HoverPopover placement="bottom" content={popoverContent}>
<Button>
<Button className="minimal">
<Icon icon="tag" />
{props.scene.tags.length}
</Button>
@ -115,7 +115,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
return (
<HoverPopover placement="bottom" content={popoverContent}>
<Button>
<Button className="minimal">
<Icon icon="user" />
{props.scene.performers.length}
</Button>
@ -133,8 +133,8 @@ export const SceneCard: React.FC<ISceneCardProps> = (
return (
<HoverPopover placement="bottom" content={popoverContent}>
<Button>
<Icon icon="tag" />
<Button className="minimal">
<Icon icon="map-marker-alt" />
{props.scene.scene_markers.length}
</Button>
</HoverPopover>
@ -150,7 +150,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
return (
<>
<hr />
<ButtonGroup className="mr-2">
<ButtonGroup className="scene-popovers">
{maybeRenderTagPopoverButton()}
{maybeRenderPerformerPopoverButton()}
{maybeRenderSceneMarkerPopoverButton()}
@ -182,7 +182,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
return (
<Card
className={`col-4 zoom-${props.zoomIndex}`}
className={`zoom-${props.zoomIndex}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
@ -216,11 +216,11 @@ export const SceneCard: React.FC<ISceneCardProps> = (
</div>
</Link>
<div className="card-section">
<h4 className="text-truncate">
<h5 className="text-truncate">
{props.scene.title
? props.scene.title
: TextUtils.fileNameFromPath(props.scene.path)}
</h4>
</h5>
<span>{props.scene.date}</span>
<p>
{TextUtils.truncate(

View file

@ -28,7 +28,7 @@ export const SceneMarkerList: React.FC = () => {
filter: ListFilterModel
) {
// query for a random scene
if (result.data && result.data.findSceneMarkers) {
if (result.data?.findSceneMarkers) {
const { count } = result.data.findSceneMarkers;
const index = Math.floor(Math.random() * count);
@ -37,10 +37,7 @@ export const SceneMarkerList: React.FC = () => {
filterCopy.currentPage = index + 1;
const singleResult = await StashService.queryFindSceneMarkers(filterCopy);
if (
singleResult &&
singleResult.data &&
singleResult.data.findSceneMarkers &&
singleResult.data.findSceneMarkers.scene_markers.length === 1
singleResult?.data?.findSceneMarkers?.scene_markers?.length === 1
) {
// navigate to the scene player page
const url = NavUtils.makeSceneMarkerUrl(

View file

@ -16,16 +16,14 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
) => {
const Toast = useToast();
const [rating, setRating] = useState<string>("");
const [studioId, setStudioId] = useState<string | undefined>(undefined);
const [performerIds, setPerformerIds] = useState<string[] | undefined>(
undefined
);
const [tagIds, setTagIds] = useState<string[] | undefined>(undefined);
const [studioId, setStudioId] = useState<string>();
const [performerIds, setPerformerIds] = useState<string[]>();
const [tagIds, setTagIds] = useState<string[]>();
const [updateScenes] = StashService.useBulkSceneUpdate(getSceneInput());
// Network state
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
function getSceneInput(): GQL.BulkSceneUpdateInput {
// need to determine what we are actually setting on each scene
@ -184,14 +182,14 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
function updateScenesEditState(state: GQL.SlimSceneDataFragment[]) {
let updateRating = "";
let updateStudioId: string | undefined;
let updateStudioId: string|undefined;
let updatePerformerIds: string[] = [];
let updateTagIds: string[] = [];
let first = true;
state.forEach((scene: GQL.SlimSceneDataFragment) => {
const thisRating = scene.rating ? scene.rating.toString() : "";
const thisStudio = scene.studio ? scene.studio.id : undefined;
const thisRating = scene.rating?.toString() ?? "";
const thisStudio = scene?.studio?.id;
if (first) {
updateRating = thisRating;
@ -231,76 +229,76 @@ export const SceneSelectedOptions: React.FC<IListOperationProps> = (
useEffect(() => {
updateScenesEditState(props.selected);
setIsLoading(false);
}, [props.selected]);
function renderMultiSelect(
type: "performers" | "tags",
initialIds: string[] | undefined
ids: string[] | undefined
) {
return (
<FilterSelect
type={type}
isMulti
isClearable={false}
onSelect={items => {
const ids = items.map(i => i.id);
const itemIDs = items.map(i => i.id);
switch (type) {
case "performers":
setPerformerIds(ids);
setPerformerIds(itemIDS);
break;
case "tags":
setTagIds(ids);
setTagIds(itemIDs);
break;
}
}}
initialIds={initialIds ?? []}
ids={ids ?? []}
/>
);
}
if(isLoading)
return <Spinner animation="border" variant="light" />;
function render() {
return (
<>
{isLoading ? <Spinner animation="border" variant="light" /> : undefined}
<div className="operation-container">
<Form.Group controlId="rating" className="operation-item">
<Form.Label>Rating</Form.Label>
<Form.Control
as="select"
onChange={(event: any) => setRating(event.target.value)}
>
{["", 1, 2, 3, 4, 5].map(opt => (
<option selected={opt === rating} value={opt}>
{opt}
</option>
))}
</Form.Control>
</Form.Group>
<div className="operation-container">
<Form.Group controlId="rating" className="operation-item rating-operation">
<Form.Label>Rating</Form.Label>
<Form.Control
as="select"
onChange={(event: any) => setRating(event.target.value)}
>
{["", '1', '2', '3', '4', '5'].map(opt => (
<option selected={opt === rating} value={opt}>
{opt}
</option>
))}
</Form.Control>
</Form.Group>
<Form.Group controlId="studio" className="operation-item">
<Form.Label>Studio</Form.Label>
<StudioSelect
onSelect={items => setStudioId(items[0]?.id)}
initialIds={studioId ? [studioId] : []}
/>
</Form.Group>
<Form.Group controlId="studio" className="operation-item">
<Form.Label>Studio</Form.Label>
<StudioSelect
onSelect={items => setStudioId(items[0]?.id)}
ids={studioId ? [studioId] : []}
/>
</Form.Group>
<Form.Group className="opeation-item" controlId="performers">
<Form.Label>Performers</Form.Label>
{renderMultiSelect("performers", performerIds)}
</Form.Group>
<Form.Group className="operation-item" controlId="performers">
<Form.Label>Performers</Form.Label>
{renderMultiSelect("performers", performerIds)}
</Form.Group>
<Form.Group className="operation-item" controlId="performers">
<Form.Label>Performers</Form.Label>
{renderMultiSelect("tags", tagIds)}
</Form.Group>
<Form.Group className="operation-item" controlId="performers">
<Form.Label>Tags</Form.Label>
{renderMultiSelect("tags", tagIds)}
</Form.Group>
<ButtonGroup className="operation-item">
<Button variant="primary" onClick={onSave}>
Apply
</Button>
</ButtonGroup>
</div>
</>
<Button variant="primary" onClick={onSave} className="apply-operation">
Apply
</Button>
</div>
);
}

View file

@ -0,0 +1,28 @@
.scene-popovers {
display: flex;
justify-content: center;
margin-bottom: 10px;
button {
padding-top: 3px;
padding-bottom: 3px;
}
svg {
margin-right: 7px;
}
}
.operation-container {
.operation-item {
min-width: 200px;
}
.rating-operation {
min-width: 20px;
}
.apply-operation {
margin-top: 2rem;
}
}

View file

@ -3,6 +3,7 @@
@import "styles/shared/details";
@import "styles/range";
@import "styles/scrollbars";
@import "styles/variables";
@ -17,7 +18,6 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100vh;
background: $dark-gray2;
}
code {
@ -37,10 +37,6 @@ code {
margin: 0;
}
& .bp3-button.favorite .bp3-icon {
color: #ff7373 !important
}
& .performer-list-thumbnail {
min-width: 50px;
height: 100px;
@ -55,58 +51,59 @@ code {
text-align: center;
vertical-align: middle;
}
}
.grid-item {
// flex: auto;
width: 320px;
min-width: 185px;
margin: 0px 0 $pt-grid-size $pt-grid-size;
overflow: hidden;
.card {
margin: 0 0 10px 10px;
overflow: hidden;
&.wall {
width: calc(20%);
margin: 0;
}
&.zoom-0 {
width: 15rem;
&.zoom-0 {
width: 240px;
& .previewable {
max-height: 11.25rem;
}
& .previewable.portrait {
max-height: 11.25rem;
}
}
&.zoom-1 {
width: 20rem;
& .previewable {
max-height: 180px;
& .previewable {
max-height: 15rem;
}
& .previewable.portrait {
height: 15rem;
}
}
& .previewable.portrait {
height: 180px;
}
}
&.zoom-1 {
width: 320px;
&.zoom-2 {
width: 30rem;
& .previewable {
max-height: 240px;
& .previewable {
max-height: 22.5rem;
}
& .previewable.portrait {
height: 22.5rem;
}
}
& .previewable.portrait {
height: 240px;
}
}
&.zoom-2 {
width: 480px;
&.zoom-3 {
width: 40rem;
& .previewable {
max-height: 360px;
& .previewable {
max-height: 30rem;
}
& .previewable.portrait {
height: 30rem;
}
}
& .previewable.portrait {
height: 360px;
}
}
&.zoom-3 {
width: 640px;
& .previewable {
max-height: 480px;
}
& .previewable.portrait {
height: 480px;
.card-select {
position: absolute;
padding-left: 15px;
margin-top: -12px;
z-index: 1;
opacity: 0.5;
width: 1.2rem;
}
}
}
@ -125,14 +122,6 @@ code {
height: 240px;
}
.grid-item label.card-select {
position: absolute;
padding-left: 15px;
margin-top: -12px;
z-index: 9;
opacity: 0.5;
}
.video-container {
width: 100%;
height: 100%;
@ -336,6 +325,7 @@ span.block {
.performer-tag-container {
margin: 5px;
display: inline-block;
}
.performer-tag.image {
@ -548,7 +538,7 @@ span.block {
background-color: #30404d;
border-radius: 3px;
box-shadow: 0 0 0 1px rgba(16,22,26,.4), 0 0 0 rgba(16,22,26,0), 0 0 0 rgba(16,22,26,0);
padding: 20px;
padding: 20px 20px 0px 20px;
}
.toast-container {

View file

@ -0,0 +1,94 @@
input[type=range] {
height: 22px;
-webkit-appearance: none;
margin: 10px 0;
background-color: transparent;
border-color: transparent;
}
input[type=range]:focus {
border: inherit;
box-shadow: none;
outline: none;
background-color: transparent;
border-color: transparent;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
animate: 0.2s;
box-shadow: 0px 0px 0px #000000;
background: #137cbd;
border-radius: 25px;
border: 0px solid #000101;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 5px;
background: #394B59;
cursor: pointer;
-webkit-appearance: none;
margin-top: -5px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #137cbd;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 6px;
cursor: pointer;
animate: 0.2s;
box-shadow: 0px 0px 0px #000000;
background: #137cbd;
border-radius: 25px;
border: 0px solid #000101;
}
input[type=range]::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 5px;
background: #394B59;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 6px;
cursor: pointer;
animate: 0.2s;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #137cbd;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000;
}
input[type=range]::-ms-fill-upper {
background: #137cbd;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000;
}
input[type=range]::-ms-thumb {
margin-top: 1px;
box-shadow: 0px 0px 0px #000000;
border: 0px solid #000000;
height: 16px;
width: 16px;
border-radius: 5px;
background: #394B59;
cursor: pointer;
}
input[type=range]:focus::-ms-fill-lower {
background: #137cbd;
}
input[type=range]:focus::-ms-fill-upper {
background: #137cbd;
}

View file

@ -1,9 +1,80 @@
/* Blueprint dark theme */
$secondary: #394b59;
$theme-colors: (
primary: #137cbd,
secondary: $secondary,
success: #0f9960,
warning: #d9822b,
danger: #db3737,
dark: #394b59
);
$body-bg: #202b33;
$text-muted: #bfccd6;
$link-color: #48aff0;
$link-hover-color: #48aff0;
$text-color: f5f8fa;
$text-color: #f5f8fa;
$pre-color: $text-color;
$navbar-dark-color: rgb(245, 248, 250);
$input-bg: $secondary;
$input-color: #f5f8fa;
$popover-bg: $secondary;
@import "node_modules/bootstrap/scss/bootstrap";
.btn.active:not(.disabled),
.btn.active.minimal:not(.disabled) {
background-color: rgba(138,155,168,.3);
color: #f5f8fa;
}
a.minimal,
button.minimal {
background: none;
border: none;
color: $text-color;
transition: none;
&:hover {
background: rgba(138,155,168,.15);
color: $text-color;
}
&:active {
background: rgba(138,155,168,.3);
color: $text-color;
}
}
input.form-control {
background-color: rgba(16, 22, 26, 0.3);
}
.form-control {
border-color: rgba(16,22,26,.4);
}
.dropdown-toggle:after {
content: none;
}
nav .svg-inline--fa {
margin-right: 7px;
}
hr {
margin: 5px 0;
}
.table {
th {
border-top: none;
}
thead {
th {
border-bottom-width: 1px;
}
}
}