This commit is contained in:
danielgrabowski 2019-11-07 11:52:27 +01:00
parent 527be316ff
commit 7ee7ebc4b3
31 changed files with 127 additions and 388 deletions

View File

@ -17,7 +17,6 @@ namespace Squirrowse.Client
return Host.CreateDefaultBuilder(args) return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => .ConfigureServices((hostContext, services) =>
{ {
services.AddHostedService<Worker>(); services.AddHostedService<Worker>();
services.AddSingleton<IConnectionManager>(x => services.AddSingleton<IConnectionManager>(x =>
new ConnectionManager("http://localhost", 5000)); //keep as transient for now new ConnectionManager("http://localhost", 5000)); //keep as transient for now

View File

@ -10,12 +10,13 @@ namespace Squirrowse.Client.Service
{ {
public class ActionDispatcher : IActionDispatcher public class ActionDispatcher : IActionDispatcher
{ {
private readonly ICameraService camera;
private readonly IConnectionManager connectionManager; private readonly IConnectionManager connectionManager;
private readonly ILogger<ActionDispatcher> logger; private readonly ILogger<ActionDispatcher> logger;
private readonly HubConnection session; private readonly HubConnection session;
private readonly ICameraService camera;
public ActionDispatcher(ILogger<ActionDispatcher> logger, IConnectionManager connectionManager,ICameraService camera) public ActionDispatcher(ILogger<ActionDispatcher> logger, IConnectionManager connectionManager,
ICameraService camera)
{ {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.logger = logger; this.logger = logger;

View File

@ -23,12 +23,7 @@ namespace Squirrowse.Client
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
for (int i = 0; i < 100; i++) for (var i = 0; i < 100; i++) await _connectionManager.InitConnection(ConnectionType.Client);
{
await _connectionManager.InitConnection(ConnectionType.Client);
}
} }
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)

View File

@ -9,7 +9,7 @@ namespace Squirrowse.Core
public static IServiceCollection AddCoreModule(this IServiceCollection services) public static IServiceCollection AddCoreModule(this IServiceCollection services)
{ {
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddSingleton<IConnectionManager>(x => services.AddSingleton<IConnectionManager, ConnectionManager>(x =>
new ConnectionManager("http://localhost", 5000)); new ConnectionManager("http://localhost", 5000));
return services; return services;
} }

View File

@ -2,7 +2,7 @@
{ {
public enum ConnectionType public enum ConnectionType
{ {
Unknown=-1, Unknown = -1,
Client, Client,
Server, Server,
Root Root

View File

@ -6,7 +6,7 @@
{ {
ConnectionId = connectionId; ConnectionId = connectionId;
AgentName = agentName; AgentName = agentName;
this.UserType = userType; UserType = userType;
} }
public string ConnectionId { get; set; } public string ConnectionId { get; set; }

View File

@ -31,8 +31,7 @@ namespace Squirrowse.Core.Services
{ {
if (_connection.State == HubConnectionState.Connected) return; if (_connection.State == HubConnectionState.Connected) return;
await _connection.StartAsync(); await _connection.StartAsync();
// await RegisterOnHub(type); await RegisterOnHub(type);
await SpamHub(type);
Connected = true; Connected = true;
} }
@ -46,17 +45,7 @@ namespace Squirrowse.Core.Services
private async Task RegisterOnHub(ConnectionType type) private async Task RegisterOnHub(ConnectionType type)
{ {
await _connection.SendAsync("AddUser", Environment.UserName,type); await _connection.SendAsync("AddUser", Environment.UserName, type);
}
#warning debug
private async Task SpamHub(ConnectionType type)
{
for (int i = 0; i < 100; i++)
{
await _connection.SendAsync("AddUser", Environment.UserName + $"{DateTime.Now}", type);
}
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squirrowse.Core.Models; using Squirrowse.Core.Models;
@ -7,7 +6,7 @@ namespace Squirrowse.Service.Hubs
{ {
public interface IStreamHub public interface IStreamHub
{ {
Task AddUser(string username,ConnectionType type); Task AddUser(string username, ConnectionType type);
Task UploadByteStream(IAsyncEnumerable<byte[]> stream); Task UploadByteStream(IAsyncEnumerable<byte[]> stream);
Task Startstream(string userId); Task Startstream(string userId);
Task StopStream(string userId); Task StopStream(string userId);

View File

@ -6,7 +6,7 @@ namespace Squirrowse.Service.Hubs
{ {
public interface IStreamManager public interface IStreamManager
{ {
Task AddUser(string connectionId, string agentName,ConnectionType type); Task AddUser(string connectionId, string agentName, ConnectionType type);
Task RemoveUserbyConnectionId(string connectionId); Task RemoveUserbyConnectionId(string connectionId);
Task RemoveUserByUserName(string agentName); Task RemoveUserByUserName(string agentName);
IEnumerable<User> getServerSideUsers(); IEnumerable<User> getServerSideUsers();

View File

@ -1,12 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using OpenCvSharp;
using Squirrowse.Core.Models; using Squirrowse.Core.Models;
using Squirrowse.Core.Services;
namespace Squirrowse.Service.Hubs namespace Squirrowse.Service.Hubs
{ {
@ -23,9 +20,8 @@ namespace Squirrowse.Service.Hubs
public async Task AddUser(string UserName, ConnectionType type) public async Task AddUser(string UserName, ConnectionType type)
{ {
await manager.AddUser(Context.ConnectionId, UserName,type); await manager.AddUser(Context.ConnectionId, UserName, type);
logger.LogInformation($"{nameof(AddUser)}: {UserName} of {type}"); logger.LogInformation($"{nameof(AddUser)}: {UserName} of {type}");
} }
@ -71,9 +67,7 @@ namespace Squirrowse.Service.Hubs
public async Task UploadByteStream(IAsyncEnumerable<byte[]> stream) public async Task UploadByteStream(IAsyncEnumerable<byte[]> stream)
{ {
foreach (var user in manager.getServerSideUsers()) foreach (var user in manager.getServerSideUsers())
{
await Clients.Client(user.ConnectionId).SendAsync("RecData", stream); await Clients.Client(user.ConnectionId).SendAsync("RecData", stream);
}
await foreach (var frame in stream) await foreach (var frame in stream)
{ {
logger.LogInformation($"Got frame size: {frame.Length} "); logger.LogInformation($"Got frame size: {frame.Length} ");
@ -85,42 +79,18 @@ namespace Squirrowse.Service.Hubs
{ {
if (t == ConnectionType.Client) return manager.getClientSideUsers(); if (t == ConnectionType.Client) return manager.getClientSideUsers();
if (t == ConnectionType.Server) return manager.getServerSideUsers(); if (t == ConnectionType.Server) return manager.getServerSideUsers();
throw new Exception("not found") ;
}
#warning DEBUG
public async Task<string> GetListOfTypeUserString()
{
string t = "";
var tasdf=manager.getClientSideUsers();
foreach (var h in tasdf)
{
t +=h.AgentName;
}
return t;
}
#warning DEBUG
public async Task<User> Getasuser() => manager.getClientSideUsers().FirstOrDefault();
#warning Debug
public async Task<List<User>> GetListOfTypeUserE(ConnectionType t)
{
if (t == ConnectionType.Client) return manager.getClientSideUsers().ToList();
if (t == ConnectionType.Server) return manager.getServerSideUsers().ToList();
throw new Exception("not found"); throw new Exception("not found");
} }
#warning debug
public async IAsyncEnumerable<User> GetListOfTypeUserAsync(ConnectionType t)
{
var client = manager.getClientSideUsers();
foreach (var va in client)
{
await Task.Delay(500);
yield return va;
}
}
public async Task<IEnumerable<User>> GetAllUsers() public async Task<IEnumerable<User>> GetAllUsers()
{ {
return manager.getAllUsers(); return manager.getAllUsers();
} }
public async IAsyncEnumerable<User> GetListOfTypeUserAsync(ConnectionType t)
{
var client = await GetListOfTypeUser(t);
foreach (var va in client) yield return va;
}
} }
} }

View File

@ -7,11 +7,11 @@ namespace Squirrowse.Service.Hubs
{ {
public class StreamManager : IStreamManager public class StreamManager : IStreamManager
{ {
private List<User> _users = new List<User>(); //temporary private readonly List<User> _users = new List<User>(); //temporary
public Task AddUser(string connectionId, string userName,ConnectionType type) public Task AddUser(string connectionId, string userName, ConnectionType type)
{ {
_users.Add(new User(connectionId, userName,type)); _users.Add(new User(connectionId, userName, type));
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -31,10 +31,12 @@ namespace Squirrowse.Service.Hubs
{ {
return _users.Where(user => user.UserType == ConnectionType.Server); return _users.Where(user => user.UserType == ConnectionType.Server);
} }
public IEnumerable<User> getClientSideUsers() public IEnumerable<User> getClientSideUsers()
{ {
return _users.Where(user => user.UserType == ConnectionType.Client); return _users.Where(user => user.UserType == ConnectionType.Client);
} }
public bool CheckUser(string userName) public bool CheckUser(string userName)
{ {
return _users.Any(user => user.AgentName == userName); return _users.Any(user => user.AgentName == userName);

View File

@ -1,6 +1,6 @@
<Router AppAssembly="@typeof(Program).Assembly"> <Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
</Found> </Found>
<NotFound> <NotFound>
<LayoutView Layout="@typeof(MainLayout)"> <LayoutView Layout="@typeof(MainLayout)">

View File

@ -1,15 +0,0 @@
using System;
namespace Squirrowse.Web.Data
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -1,25 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Squirrowse.Web.Data
{
public class WeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
{
var rng = new Random();
return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray());
}
}
}

View File

@ -1,16 +0,0 @@
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
}
}

View File

@ -1,16 +0,0 @@
@page "/error"
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -1,46 +0,0 @@
@page "/fetchdata"
@using Squirrowse.Web.Data
@inject WeatherForecastService ForecastService
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}

View File

@ -33,26 +33,7 @@
</div> </div>
} }
} }
else
@*<div class="card-body">
<div>
<h3 class="badge-primary">
@agents.AgentName -> @agents.UserType.ToString()
</h3>
<div style="padding-top:10px">
<button id="ViewCast" disabled="@(IsViewingCastOf(agents.AgentName))" class="btn btn-success btn-sm" @onclick="@(() => OnViewCastClicked(agents.ConnectionId))">
View cast
</button>
<button id="StopViewCast" disabled="@(!IsViewingCastOf(agents.AgentName))" class="btn btn-warning btn-sm" @onclick="@(() => OnStopViewCastClicked(agents.ConnectionId))">
Stop cast
</button>
</div>
</div>
</div>*@
else
{ {
<div class="card-body"> <div class="card-body">
<h3 class="card-header badge-warning">No Cams</h3> <h3 class="card-header badge-warning">No Cams</h3>
@ -73,48 +54,25 @@ else
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// agents=new List<User>();
await _connection.InitConnection(ConnectionType.Server); await _connection.InitConnection(ConnectionType.Server);
connection = await _connection.GetConnection(); connection = await _connection.GetConnection();
connection.On<IAsyncEnumerable<byte[]>>("RecData", OnStreamDataReceived); connection.On<IAsyncEnumerable<byte[]>>("RecData", OnStreamDataReceived);
//agents = await connection.InvokeAsync<string>("GetListOfTypeUserString");
//agents = await connection.InvokeAsync<User>("Getasuser"); await foreach (var user in connection.StreamAsync<User>("GetListOfTypeUserAsync", ConnectionType.Client).ConfigureAwait(false))
//agents = await connection.InvokeAsync<IAsyncEnumerable<User>>("GetListOfTypeUserAsync",ConnectionType.Client);
await foreach (var dupa in connection.StreamAsync<User>("GetListOfTypeUserAsync", ConnectionType.Client).ConfigureAwait(false))
{ {
agents.Add(dupa); agents.Add(user);
// await Task.Delay(300);
this.StateHasChanged(); this.StateHasChanged();
} }
//connection.On<User>("NewUser", NewUser);
//connection.On<string>("RemoveScreenCastAgent", RemoveScreenCastAgent);
//connection.On<string>("OnStreamDataReceived", OnStreamDataReceived);
//await connection.StartAsync();
} }
//cannot control enumerator
//protected override async Task OnAfterRenderAsync(bool firstRender=true)
//{
// await foreach (var dupa in connection.StreamAsync<User>("GetListOfTypeUserAsync", ConnectionType.Client))
// {
// agents.Add(dupa);
// await Task.Delay(300);
// this.StateHasChanged();
// }
//}
bool IsViewingCastOf(string agentName) bool IsViewingCastOf(string agentName)
{ {
return agentName == CurrentViewCastAgent; return agentName == CurrentViewCastAgent;
} }
//void NewUser(User agentName)
//{
// agents.Add(agentName);
// StateHasChanged();
//}
async void OnStreamDataReceived(IAsyncEnumerable<byte[]> streamData) async void OnStreamDataReceived(IAsyncEnumerable<byte[]> streamData)
{ {
await foreach (var t in streamData) await foreach (var t in streamData)

View File

@ -5,18 +5,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Squirrowse.Web</title> <title>Squirrowse.Web</title>
<base href="~/" /> <base href="~/"/>
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css"/>
<link href="css/site.css" rel="stylesheet" /> <link href="css/site.css" rel="stylesheet"/>
</head> </head>
<body> <body>
<app> <app>
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered)) @(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
</app> </app>
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
</body> </body>
</html> </html>

View File

@ -1,13 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Squirrowse.Web namespace Squirrowse.Web
{ {
@ -18,11 +10,10 @@ namespace Squirrowse.Web
CreateHostBuilder(args).Build().Run(); CreateHostBuilder(args).Build().Run();
} }
public static IHostBuilder CreateHostBuilder(string[] args) => public static IHostBuilder CreateHostBuilder(string[] args)
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); return Host.CreateDefaultBuilder(args)
}); .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
} }
} }

View File

@ -1,7 +1,7 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<div class="sidebar"> <div class="sidebar">
<NavMenu /> <NavMenu/>
</div> </div>
<div class="main"> <div class="main">

View File

@ -3,34 +3,24 @@
<button class="navbar-toggler" @onclick="ToggleNavMenu"> <button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
</div> </div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item px-3"> <li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home <span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink> </NavLink>
</li> </li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3"> <li class="nav-item px-3">
<NavLink class="nav-link" href="Hub"> <NavLink class="nav-link" href="Hub">
<span class="oi oi-list-rich" aria-hidden="true"></span> Stream <span class="oi oi-list-rich" aria-hidden="true"></span> Stream
</NavLink> </NavLink>
</li> </li>
</ul> </ul>
</div> </div>
@code { @code {
bool collapseNavMenu = true; bool collapseNavMenu = true;
string NavMenuCssClass => collapseNavMenu ? "collapse" : null; string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
@ -39,4 +29,5 @@
{ {
collapseNavMenu = !collapseNavMenu; collapseNavMenu = !collapseNavMenu;
} }
}
}

View File

@ -14,4 +14,8 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Squirrowse.Core; using Squirrowse.Core;
using Squirrowse.Web.Data;
namespace Squirrowse.Web namespace Squirrowse.Web
{ {
@ -29,10 +22,9 @@ namespace Squirrowse.Web
{ {
services.AddRazorPages(); services.AddRazorPages();
services.AddServerSideBlazor().AddHubOptions(x=>x.MaximumReceiveMessageSize= 102400000); services.AddServerSideBlazor().AddHubOptions(x => x.MaximumReceiveMessageSize = 102400000);
services.AddCoreModule(); services.AddCoreModule();
services.AddSignalR().AddMessagePackProtocol(); services.AddSignalR().AddMessagePackProtocol();
services.AddSingleton<WeatherForecastService>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -1,34 +1,28 @@
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
html, body { html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link { a, .btn-link { color: #0366d6; }
color: #0366d6;
}
.btn-primary { .btn-primary {
color: #fff;
background-color: #1b6ec2; background-color: #1b6ec2;
border-color: #1861ac; border-color: #1861ac;
color: #fff;
} }
app { app {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
} }
.top-row { .top-row {
height: 3.5rem;
display: flex;
align-items: center; align-items: center;
display: flex;
height: 3.5rem;
} }
.main { .main { flex: 1; }
flex: 1;
}
.main .top-row { .main .top-row {
background-color: #f7f7f7; background-color: #f7f7f7;
@ -36,27 +30,19 @@ app {
justify-content: flex-end; justify-content: flex-end;
} }
.main .top-row > a { .main .top-row > a { margin-left: 1.5rem; }
margin-left: 1.5rem;
}
.sidebar { .sidebar { background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); }
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.sidebar .top-row { .sidebar .top-row { background-color: rgba(0, 0, 0, 0.4); }
background-color: rgba(0,0,0,0.4);
}
.sidebar .navbar-brand { .sidebar .navbar-brand { font-size: 1.1rem; }
font-size: 1.1rem;
}
.sidebar .oi { .sidebar .oi {
width: 2rem;
font-size: 1.1rem; font-size: 1.1rem;
vertical-align: text-top;
top: -2px; top: -2px;
vertical-align: text-top;
width: 2rem;
} }
.nav-item { .nav-item {
@ -64,69 +50,51 @@ app {
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.nav-item:first-of-type { .nav-item:first-of-type { padding-top: 1rem; }
padding-top: 1rem;
}
.nav-item:last-of-type { .nav-item:last-of-type { padding-bottom: 1rem; }
padding-bottom: 1rem;
}
.nav-item a { .nav-item a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center; align-items: center;
border-radius: 4px;
color: #d7d7d7;
display: flex;
height: 3rem;
line-height: 3rem; line-height: 3rem;
} }
.nav-item a.active { .nav-item a.active {
background-color: rgba(255,255,255,0.25); background-color: rgba(255, 255, 255, 0.25);
color: white; color: white;
} }
.nav-item a:hover { .nav-item a:hover {
background-color: rgba(255,255,255,0.1); background-color: rgba(255, 255, 255, 0.1);
color: white; color: white;
} }
.content { .content { padding-top: 1.1rem; }
padding-top: 1.1rem;
}
.navbar-toggler { .navbar-toggler { background-color: rgba(255, 255, 255, 0.1); }
background-color: rgba(255, 255, 255, 0.1);
}
.valid.modified:not([type=checkbox]) { .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; }
outline: 1px solid #26b050;
}
.invalid { .invalid { outline: 1px solid red; }
outline: 1px solid red;
}
.validation-message { .validation-message { color: red; }
color: red;
}
@media (max-width: 767.98px) { @media (max-width: 767.98px) {
.main .top-row { .main .top-row { display: none; }
display: none;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {
app { app { flex-direction: row; }
flex-direction: row;
}
.sidebar { .sidebar {
width: 250px;
height: 100vh; height: 100vh;
position: sticky; position: sticky;
top: 0; top: 0;
width: 250px;
} }
.main .top-row { .main .top-row {
@ -139,9 +107,7 @@ app {
padding-right: 1.5rem !important; padding-right: 1.5rem !important;
} }
.navbar-toggler { .navbar-toggler { display: none; }
display: none;
}
.sidebar .collapse { .sidebar .collapse {
/* Never collapse the sidebar for wide screens */ /* Never collapse the sidebar for wide screens */