27 changed files with 641 additions and 5 deletions
-4
View File
@@ -3,9 +3,6 @@ root = true
# All files # All files
[*] [*]
indent_style = space indent_style = space
# Xml files
[*.xml]
indent_size = 2 indent_size = 2
# C# files # C# files
@@ -14,7 +11,6 @@ indent_size = 2
#### Core EditorConfig Options #### #### Core EditorConfig Options ####
# Indentation and spacing # Indentation and spacing
indent_size = 2
tab_width = 2 tab_width = 2
# New line preferences # New line preferences
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": [
"onclick",
"prerender",
"rendermode"
]
}
+17
View File
@@ -45,6 +45,21 @@ builder.Services.AddRateLimiter(
} }
); );
// TODO: Make CORS more restrictive
// only allow requests from the web
// client
const string corsPolicyName = "cors";
builder.Services.AddCors(o =>
{
o.AddPolicy(corsPolicyName, p =>
{
p.AllowAnyOrigin()
.AllowAnyOrigin()
.AllowAnyMethod();
});
});
var app = builder.Build(); var app = builder.Build();
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@@ -57,6 +72,8 @@ if (app.Environment.IsProduction())
app.UseRateLimiter(); app.UseRateLimiter();
} }
app.UseCors(corsPolicyName);
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseStatusCodePages(); app.UseStatusCodePages();
@@ -1,5 +1,6 @@
namespace AltGen.Console.Generate; namespace AltGen.Console.Generate;
// TODO: Use shared library version of this service
sealed class AltGenService(HttpClient client) : IAltGenService sealed class AltGenService(HttpClient client) : IAltGenService
{ {
static readonly JsonSerializerOptions JsonOptions = new() static readonly JsonSerializerOptions JsonOptions = new()
+1 -1
View File
@@ -3,4 +3,4 @@ namespace AltGen.Console.Generated;
static class Constants static class Constants
{ {
public const string AltGenApiUri = "https://localhost:7297"; public const string AltGenApiUri = "https://localhost:7297";
} }
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,47 @@
using System.Net.Http.Headers;
using System.Text.Json;
namespace AltGen.Shared.Generate;
public sealed class AltGenService(HttpClient client) : IAltGenService
{
static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
readonly HttpClient _client = client;
public async Task<string> GenerateAltTextAsync(GenerateAltTextRequest req)
{
var byteContent = new ByteArrayContent(req.Image);
byteContent.Headers.ContentType = new MediaTypeHeaderValue(req.ContentType);
var request = new HttpRequestMessage(HttpMethod.Post, "/generate")
{
Content = new MultipartFormDataContent
{
{ new StringContent(req.Provider), "provider" },
{ new StringContent(req.ProviderKey), "providerKey" },
{ byteContent, "file", req.FileName }
}
};
var response = await _client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode is false)
{
throw new AltTextException("Failed to generate alt text.");
}
var altTextResponse = JsonSerializer.Deserialize<AltTextResponse>(content, JsonOptions) ?? throw new AltTextException("Failed to deserialize response.");
return altTextResponse.AltText;
}
}
record AltTextResponse(string AltText);
class AltTextException(string message) : Exception(message)
{
}
@@ -0,0 +1,9 @@
namespace AltGen.Shared.Generate;
public record GenerateAltTextRequest(
string Provider,
string ProviderKey,
string FileName,
byte[] Image,
string ContentType
);
@@ -0,0 +1,6 @@
namespace AltGen.Shared.Generate;
public interface IAltGenService
{
public Task<string> GenerateAltTextAsync(GenerateAltTextRequest req);
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AltGen.Shared\AltGen.Shared.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,126 @@
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@inject IJSRuntime JsRuntime
@implements IAsyncDisposable
@* TODO: Create providers context.
- Load providers and present in table
- Allow form to add new provider
- Handle changing default provider
*@
<header>
<button type="button" @onclick="HandleConfigButtonClick" class="config-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z" />
</svg>
</button>
</header>
<dialog @ref="dialogElement">
<div class="providers-container">
<button type="button" @onclick="HandleCloseButtonClick" class="close-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z" />
</svg>
</button>
<div class="providers-table-container">
<table>
<thead>
<tr>
<th>Provider</th>
<th>Provider Key</th>
<th>Default</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < 5; i++)
{
<tr>
<td>Provider @(i + 1)</td>
<td>Key @(i + 1)</td>
<td><input type="checkbox" /></td>
</tr>
}
</tbody>
</table>
</div>
<div class="form-container">
<form>
<div class="form-group">
<label for="provider">Provider</label>
<input type="text" name="provider" id="provider">
</div>
<div class="form-group">
<label>Provider Key</label>
<input type="password" name="providerKey" id="providerKey">
</div>
<div class="form-group row">
<input type="checkbox" name="defaultProvider" id="defaultProvider">
<label for="">Default</label>
</div>
<button type="submit">Add</button>
</form>
</div>
</div>
</dialog>
@code {
private IJSObjectReference? _jsModule;
private ElementReference dialogElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_jsModule = await JsRuntime.InvokeAsync<IJSObjectReference>(JsFunctions.Import, ComponentAssets.JsModule);
}
}
private async Task HandleConfigButtonClick()
{
if (_jsModule is null)
{
return;
}
await _jsModule.InvokeVoidAsync(JsFunctions.ShowDialog, dialogElement);
}
private async Task HandleCloseButtonClick()
{
if (_jsModule is null)
{
return;
}
await _jsModule.InvokeVoidAsync(JsFunctions.CloseDialog, dialogElement);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (_jsModule is not null)
{
try
{
await _jsModule.DisposeAsync();
}
catch (JSDisconnectedException)
{
}
}
}
static class ComponentAssets
{
public const string JsModule = "./Components/Layout/Header.razor.js";
}
static class JsFunctions
{
public const string Import = "import";
public const string ShowDialog = "showDialog";
public const string CloseDialog = "closeDialog";
}
}
@@ -0,0 +1,59 @@
.config-button,
.close-button{
display: flex;
align-items: center;
justify-content: center;
padding: 0.25rem;
border-radius: 0.25rem;
}
.config-button svg,
.close-button svg {
height: 1rem;
width: 1rem;
}
.providers-container {
display: flex;
flex-direction: column;
gap: 1.25rem;
padding: 1.25rem;
position: relative;
}
.close-button {
position: absolute;
top: 0;
right: 0;
background-color: transparent;
border: none;
cursor: pointer;
}
.form-container {
display: flex;
flex-direction: column;
}
.form-container form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.form-group.row {
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
.form-group label {
font-size: 0.875rem;
font-weight: bold;
}
@@ -0,0 +1,17 @@
/**
* @summary Shows a dialog element by calling the `showModal` method on it.
* @param {HTMLDialogElement} dialog - The dialog element to show
* @returns {void}
*/
export function showDialog(dialog) {
dialog.showModal();
}
/**
* @summary Closes a dialog element by calling the `close` method on it.
* @param {HTMLDialogElement} dialog - The dialog element to close
* @returns {void}
*/
export function closeDialog(dialog) {
dialog.close();
}
@@ -0,0 +1,55 @@
@using AltGen.Shared.Generate
@inject ILogger<Home> Logger
@inject IAltGenService AltGenService
@page "/"
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
@* TODO: Present configured providers a dropdown.
If there is a default provider, use it.
*@
<PageTitle>AltGen.Web</PageTitle>
<h1>AI Generated Alt Text</h1>
<form method="post">
<div>
<label for="provider">Provider</label>
<input type="text" id="provider" name="provider" required @bind="_provider" />
</div>
<div>
<label for="providerKey">Provider Key</label>
<input type="password" id="providerKey" name="providerKey" required @bind="_providerKey" />
</div>
<InputFile OnChange="HandleFileChange" />
</form>
@code {
private string? _provider;
private string? _providerKey;
private async Task HandleFileChange(InputFileChangeEventArgs e)
{
var file = e.File;
if (_provider is null || _providerKey is null || file is null)
{
return;
}
var fileBytes = new byte[file.Size];
using var stream = file.OpenReadStream();
await stream.ReadAsync(fileBytes, 0, (int)file.Size);
var altText = await AltGenService.GenerateAltTextAsync(new(
_provider,
_providerKey,
file.Name,
fileBytes,
file.ContentType
));
// TODO: Present on the screen to the user
Logger.LogInformation(altText);
}
}
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using AltGen.Shared.Generate;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// TODO: Read base url
// from configuration
builder.Services.AddHttpClient<IAltGenService, AltGenService>(c => c.BaseAddress = new("https://localhost:7297"));
await builder.Build().RunAsync();
+9
View File
@@ -0,0 +1,9 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AltGen.Web.Client
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AltGen.Web.Client\AltGen.Web.Client.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AltGen.Shared\AltGen.Shared.csproj" />
</ItemGroup>
</Project>
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["AltGen.Web.styles.css"]" />
<ImportMap />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@@ -0,0 +1,14 @@
@inherits LayoutComponentBase
@using AltGen.Web.Client.Components.Layout
<Header />
<main>
@Body
</main>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
@@ -0,0 +1,20 @@
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -0,0 +1,38 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<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>
@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
+11
View File
@@ -0,0 +1,11 @@
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="typeof(Layout.MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
+11
View File
@@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AltGen.Web
@using AltGen.Web.Client
@using AltGen.Web.Components
+30
View File
@@ -0,0 +1,30 @@
using AltGen.Web.Components;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(AltGen.Web.Client._Imports).Assembly);
app.Run();
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7198;http://localhost:5183",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5183",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
}
}
+44
View File
@@ -0,0 +1,44 @@
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
.form-floating>.form-control-plaintext::placeholder,
.form-floating>.form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating>.form-control-plaintext:focus::placeholder,
.form-floating>.form-control:focus::placeholder {
text-align: start;
}
dialog {
padding: 0;
}
+18
View File
@@ -11,6 +11,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Console", "AltGen.Co
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Console.Tests", "AltGen.Console.Tests\AltGen.Console.Tests.csproj", "{F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Console.Tests", "AltGen.Console.Tests\AltGen.Console.Tests.csproj", "{F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Web", "AltGen.Web\AltGen.Web.csproj", "{CD09C06D-AB87-4C4E-8AD7-2694ED9BF5C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Web.Client", "AltGen.Web.Client\AltGen.Web.Client.csproj", "{DCE07E79-23A4-490E-A796-EA140D2040C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AltGen.Shared", "AltGen.Shared\AltGen.Shared.csproj", "{68D4BC5D-A6FE-41CF-B744-1DB83CCFBC89}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -36,5 +42,17 @@ Global
{F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Release|Any CPU.Build.0 = Release|Any CPU {F7DC6A70-907B-448A-A69D-4E2E80FA3FD3}.Release|Any CPU.Build.0 = Release|Any CPU
{CD09C06D-AB87-4C4E-8AD7-2694ED9BF5C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD09C06D-AB87-4C4E-8AD7-2694ED9BF5C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD09C06D-AB87-4C4E-8AD7-2694ED9BF5C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD09C06D-AB87-4C4E-8AD7-2694ED9BF5C6}.Release|Any CPU.Build.0 = Release|Any CPU
{DCE07E79-23A4-490E-A796-EA140D2040C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DCE07E79-23A4-490E-A796-EA140D2040C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCE07E79-23A4-490E-A796-EA140D2040C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCE07E79-23A4-490E-A796-EA140D2040C1}.Release|Any CPU.Build.0 = Release|Any CPU
{68D4BC5D-A6FE-41CF-B744-1DB83CCFBC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68D4BC5D-A6FE-41CF-B744-1DB83CCFBC89}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68D4BC5D-A6FE-41CF-B744-1DB83CCFBC89}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68D4BC5D-A6FE-41CF-B744-1DB83CCFBC89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal