From 62a8012919e229c4e090a14b9619cbc037cabec0 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 16:16:20 +1000 Subject: [PATCH 01/19] feat: store outputted ffmpeg logs in a encode --- .../EncodeOperationOutputtedDataActivity.cs | 50 +++++++++++ .../Encodes/Sagas/EncodeStateMachine.cs | 4 + .../Sagas/EncodeStateMachineDefinition.cs | 9 +- .../src/Domain/Aggregates/Encodes/Encode.cs | 83 ++++++++++++++++++- .../Types/Encodes/Objects/EncodeType.cs | 8 +- .../Encoding/Jobs/EncodeFileConsumer.cs | 15 ++++ .../Events/EncodeOperationOutputtedEvent.cs | 10 +++ 7 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 src/Service.Dashboard/src/Application.Components/Encodes/Sagas/Activities/EncodeOperationOutputtedDataActivity.cs create mode 100644 src/Service.Encoder/src/Application.Contracts/Encoding/Events/EncodeOperationOutputtedEvent.cs diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/Activities/EncodeOperationOutputtedDataActivity.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/Activities/EncodeOperationOutputtedDataActivity.cs new file mode 100644 index 00000000..ca89f786 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/Activities/EncodeOperationOutputtedDataActivity.cs @@ -0,0 +1,50 @@ +using Giantnodes.Infrastructure.Uow.Services; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes.Repositories; +using Giantnodes.Service.Dashboard.Persistence.Sagas; +using Giantnodes.Service.Encoder.Application.Contracts.Encoding.Events; +using MassTransit; + +namespace Giantnodes.Service.Dashboard.Application.Components.Encodes.Sagas.Activities; + +public class EncodeOperationOutputtedDataActivity : IStateMachineActivity +{ + private readonly IUnitOfWorkService _uow; + private readonly IEncodeRepository _repository; + + public EncodeOperationOutputtedDataActivity(IUnitOfWorkService uow, IEncodeRepository repository) + { + _uow = uow; + _repository = repository; + } + + public void Probe(ProbeContext context) + { + context.CreateScope(KebabCaseEndpointNameFormatter.Instance.Message()); + } + + public void Accept(StateMachineVisitor visitor) + { + visitor.Visit(this); + } + + public async Task Execute( + BehaviorContext context, + IBehavior next) + { + using var uow = await _uow.BeginAsync(context.CancellationToken); + var encode = await _repository.SingleAsync(x => x.Id == context.Saga.EncodeId); + + encode.AppendOutputLog(context.Message.Data); + + await uow.CommitAsync(context.CancellationToken); + await next.Execute(context); + } + + public Task Faulted( + BehaviorExceptionContext context, + IBehavior next) + where TException : Exception + { + return next.Faulted(context); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachine.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachine.cs index a7fe68db..2884f019 100644 --- a/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachine.cs +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachine.cs @@ -21,6 +21,7 @@ public EncodeStateMachine() Event(() => Built); Event(() => Heartbeat); Event(() => Progressed); + Event(() => Outputted); Event(() => Completed); Event(() => Failed); @@ -73,6 +74,8 @@ public EncodeStateMachine() .Finalize()); DuringAny( + When(Outputted) + .Activity(context => context.OfType()), When(Failed) .Then(context => context.Saga.FailedReason = context.Message.Exceptions.Message) .Activity(context => context.OfInstanceType()) @@ -95,6 +98,7 @@ public EncodeStateMachine() public required Event Built { get; set; } public required Event Heartbeat { get; set; } public required Event Progressed { get; set; } + public required Event Outputted { get; set; } public required Event Completed { get; set; } public required Event Cancelled { get; set; } public required Event Failed { get; set; } diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachineDefinition.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachineDefinition.cs index 7d84e7e0..a4250d3d 100644 --- a/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachineDefinition.cs +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Sagas/EncodeStateMachineDefinition.cs @@ -1,5 +1,5 @@ -using Giantnodes.Service.Dashboard.Persistence.DbContexts; using Giantnodes.Service.Dashboard.Persistence.Sagas; +using Giantnodes.Service.Encoder.Application.Contracts.Encoding.Events; using MassTransit; namespace Giantnodes.Service.Dashboard.Application.Components.Encodes.Sagas; @@ -11,8 +11,11 @@ protected override void ConfigureSaga( ISagaConfigurator sagaConfigurator, IRegistrationContext context) { - endpointConfigurator.ConcurrentMessageLimit = 3; - endpointConfigurator.UseMessageRetry(r => r.Interval(3, TimeSpan.FromSeconds(3))); + + var partition = sagaConfigurator.CreatePartitioner(1); + endpointConfigurator.UsePartitioner(partition, p => p.Message.JobId); + endpointConfigurator.UsePartitioner(partition, p => p.Message.JobId); + endpointConfigurator.UsePartitioner(partition, p => p.Message.JobId); } } \ No newline at end of file diff --git a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs index c2a7ab2d..79ed016b 100644 --- a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs +++ b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs @@ -16,36 +16,85 @@ public class Encode : AggregateRoot, ITimestampableEntity { private readonly List _snapshots = new(); + /// + /// The file being encoded. + /// public FileSystemFile File { get; private set; } + /// + /// The recipe used for encoding. + /// public Recipe Recipe { get; private set; } + /// + /// The current encoding speed. + /// public EncodeSpeed? Speed { get; private set; } + /// + /// The current status of the encoding process. + /// public EncodeStatus Status { get; private set; } + /// + /// The machine performing the encoding. + /// + public Machine? Machine { get; private set; } + + /// + /// The current progress percentage of the encoding process. + /// public float? Percent { get; private set; } - public string? FfmpegCommand { get; private set; } + /// + /// The ffmpeg command used for the encoding. + /// + public string? Command { get; private set; } - public Machine? Machine { get; private set; } + /// + /// The ffmpeg output log of the encoding process. + /// + public string? Output { get; private set; } + /// + /// The timestamp when the encoding started. + /// public DateTime? StartedAt { get; private set; } + /// + /// The timestamp when the encoding failed. + /// public DateTime? FailedAt { get; private set; } + /// + /// The reason for the encoding failure. + /// public string? FailureReason { get; private set; } + /// + /// The timestamp when the encoding was degraded. + /// public DateTime? DegradedAt { get; private set; } + /// + /// The timestamp when the encoding was cancelled. + /// public DateTime? CancelledAt { get; private set; } + /// + /// The timestamp when the encoding was completed. + /// public DateTime? CompletedAt { get; private set; } + /// public DateTime CreatedAt { get; private set; } + /// public DateTime? UpdatedAt { get; private set; } + /// + /// A collection of snapshots taken during the encoding process. + /// public IReadOnlyCollection Snapshots { get; private set; } private Encode() @@ -113,6 +162,10 @@ public void SetCancelled(DateTime when) DomainEvents.Add(new EncodeCancelledEvent { EncodeId = Id }); } + /// + /// Sets the progress of the encoding process. + /// + /// The current progress percentage. public void SetProgress(float progress) { if (Status is not EncodeStatus.Encoding) @@ -123,6 +176,10 @@ public void SetProgress(float progress) Percent = progress; } + /// + /// Sets the encoding speed. + /// + /// The encoding speed. public void SetSpeed(EncodeSpeed speed) { if (Status is not EncodeStatus.Encoding) @@ -139,14 +196,34 @@ public void SetSpeed(EncodeSpeed speed) }); } + /// + /// Sets the ffmpeg conversion command and the machine performing the encoding. + /// + /// The machine performing the encoding. + /// The ffmpeg command for the encoding. public void SetFfmpegConversion(Machine machine, string command) { Guard.Against.NullOrWhiteSpace(command); Machine = machine; - FfmpegCommand = command; + Command = command; + } + + /// + /// Appends the given ffmpeg output to the encoding log. + /// + /// The ffmpeg output to be appended. + public void AppendOutputLog(string output) + { + Guard.Against.NullOrWhiteSpace(output); + + Output = string.Join(Environment.NewLine, Output, output); } + /// + /// Adds a snapshot taken during the encoding process. + /// + /// The snapshot to be added. public void AddSnapshot(EncodeSnapshot snapshot) { _snapshots.Add(snapshot); diff --git a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Objects/EncodeType.cs b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Objects/EncodeType.cs index f46af258..834512b2 100644 --- a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Objects/EncodeType.cs +++ b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Objects/EncodeType.cs @@ -17,6 +17,9 @@ protected override void Configure(IObjectTypeDescriptor descriptor) descriptor .Field(p => p.File); + descriptor + .Field(p => p.Recipe); + descriptor .Field(p => p.Status); @@ -27,7 +30,10 @@ protected override void Configure(IObjectTypeDescriptor descriptor) .Field(p => p.Speed); descriptor - .Field(p => p.FfmpegCommand); + .Field(p => p.Command); + + descriptor + .Field(p => p.Output); descriptor .Field(p => p.Machine); diff --git a/src/Service.Encoder/src/Application.Components/Encoding/Jobs/EncodeFileConsumer.cs b/src/Service.Encoder/src/Application.Components/Encoding/Jobs/EncodeFileConsumer.cs index adb729a9..5329907d 100644 --- a/src/Service.Encoder/src/Application.Components/Encoding/Jobs/EncodeFileConsumer.cs +++ b/src/Service.Encoder/src/Application.Components/Encoding/Jobs/EncodeFileConsumer.cs @@ -86,6 +86,21 @@ public async Task Run(JobContext context) } }; + conversion.OnDataReceived += async (_, args) => + { + if (string.IsNullOrWhiteSpace(args.Data)) + return; + + var @event = new EncodeOperationOutputtedEvent + { + JobId = context.JobId, + CorrelationId = context.Job.CorrelationId, + Data = args.Data + }; + + await context.Publish(@event, context.CancellationToken); + }; + ConversionProgressEventArgs? progress = null; conversion.OnProgress += async (_, args) => { diff --git a/src/Service.Encoder/src/Application.Contracts/Encoding/Events/EncodeOperationOutputtedEvent.cs b/src/Service.Encoder/src/Application.Contracts/Encoding/Events/EncodeOperationOutputtedEvent.cs new file mode 100644 index 00000000..37838555 --- /dev/null +++ b/src/Service.Encoder/src/Application.Contracts/Encoding/Events/EncodeOperationOutputtedEvent.cs @@ -0,0 +1,10 @@ +using Giantnodes.Infrastructure.Domain.Events; + +namespace Giantnodes.Service.Encoder.Application.Contracts.Encoding.Events; + +public sealed record EncodeOperationOutputtedEvent : IntegrationEvent +{ + public required Guid JobId { get; init; } + + public required string Data { get; init; } +} \ No newline at end of file From 6c7e0716ef01e6e601db8231eb26ef6229f2f474 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 16:17:08 +1000 Subject: [PATCH 02/19] feat: raise event on encode progression --- .../Events/RaiseEncodeProgressedTopic.cs | 25 +++++++++++++++++++ .../Encodes/Events/EncodeProgressedEvent.cs | 10 ++++++++ .../src/Domain/Aggregates/Encodes/Encode.cs | 6 +++++ .../EncodeProgressedSubscription.cs | 23 +++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeProgressedTopic.cs create mode 100644 src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeProgressedEvent.cs create mode 100644 src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeProgressedSubscription.cs diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeProgressedTopic.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeProgressedTopic.cs new file mode 100644 index 00000000..70a10dfd --- /dev/null +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeProgressedTopic.cs @@ -0,0 +1,25 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes.Repositories; +using HotChocolate.Subscriptions; +using MassTransit; + +namespace Giantnodes.Service.Dashboard.Application.Components.Encodes.Events; + +public class RaiseEncodeProgressedTopic : IConsumer +{ + private readonly IEncodeRepository _repository; + private readonly ITopicEventSender _sender; + + public RaiseEncodeProgressedTopic(IEncodeRepository repository, ITopicEventSender sender) + { + _repository = repository; + _sender = sender; + } + + public async Task Consume(ConsumeContext context) + { + var encode = await _repository.SingleAsync(x => x.Id == context.Message.EncodeId); + + await _sender.SendAsync(nameof(EncodeProgressedEvent), encode, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeProgressedEvent.cs b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeProgressedEvent.cs new file mode 100644 index 00000000..ecde7fb2 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeProgressedEvent.cs @@ -0,0 +1,10 @@ +using Giantnodes.Infrastructure.Domain.Events; + +namespace Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; + +public sealed record EncodeProgressedEvent : DomainEvent +{ + public required Guid EncodeId { get; init; } + + public required float Percent { get; set; } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs index 79ed016b..7936e128 100644 --- a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs +++ b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs @@ -174,6 +174,12 @@ public void SetProgress(float progress) Guard.Against.OutOfRange(progress, nameof(progress), 0, 1); Percent = progress; + + DomainEvents.Add(new EncodeProgressedEvent + { + EncodeId = Id, + Percent = Percent.Value + }); } /// diff --git a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeProgressedSubscription.cs b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeProgressedSubscription.cs new file mode 100644 index 00000000..0cb54f81 --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeProgressedSubscription.cs @@ -0,0 +1,23 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes; +using Giantnodes.Service.Dashboard.Persistence.DbContexts; +using Microsoft.EntityFrameworkCore; + +namespace Giantnodes.Service.Dashboard.HttpApi.Resolvers.Encodes.Subscriptions; + +[ExtendObjectType(OperationTypeNames.Subscription)] +public class EncodeProgressedSubscription +{ + [Subscribe] + [Topic(nameof(EncodeProgressedEvent))] + [UseSingleOrDefault] + [UseProjection] + [UseFiltering] + [UseSorting] + public IQueryable EncodeProgressed( + [Service] ApplicationDbContext database, + [EventMessage] Encode encode) + { + return database.Encodes.Where(x => x.Id == encode.Id).AsNoTracking(); + } +} \ No newline at end of file From 0eb574551a17ab2c900731cba99765808e988f0d Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 16:18:10 +1000 Subject: [PATCH 03/19] feat: use subscription to listen to encode progression changes --- .../tables/encoding/EncodingTable.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index 95c6fced..ad4c3f66 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -59,10 +59,17 @@ const MUTATION = graphql` } ` -const SUBSCRIPTION = graphql` - subscription EncodingTableSubscription { - encode_speed_change { +const PROGRESSED_SUBSCRIPTION = graphql` + subscription EncodingTableProgressedSubscription { + encode_progressed { percent + } + } +` + +const SPEED_SUBSCRIPTION = graphql` + subscription EncodingTableSpeedSubscription { + encode_speed_change { speed { frames bitrate @@ -87,7 +94,12 @@ const EncodingTable: React.FC = ({ $key }) => { const [commit] = useMutation(MUTATION) useSubscription({ - subscription: SUBSCRIPTION, + subscription: PROGRESSED_SUBSCRIPTION, + variables: {}, + }) + + useSubscription({ + subscription: SPEED_SUBSCRIPTION, variables: {}, }) From c97c37dca5516ccf59938bc65bd040ad86ac5c50 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 16:23:57 +1000 Subject: [PATCH 04/19] feat: add encode dialog to dashboard --- app/package.json | 2 + app/pnpm-lock.yaml | 150 ++++++++++++++++++ .../interfaces/dashboard/EncodeDialog.tsx | 88 ++++++++++ .../tables/encoded/EncodedTable.tsx | 10 +- .../components/ui/code-block/CodeBlock.tsx | 22 +++ .../ui/encode-badges/EncodeBadges.tsx | 42 ++--- .../ui/encode-badges/EncodeStatusBadge.tsx | 50 ++++++ app/src/libraries/dayjs/index.ts | 2 + 8 files changed, 331 insertions(+), 35 deletions(-) create mode 100644 app/src/components/interfaces/dashboard/EncodeDialog.tsx create mode 100644 app/src/components/ui/code-block/CodeBlock.tsx create mode 100644 app/src/components/ui/encode-badges/EncodeStatusBadge.tsx diff --git a/app/package.json b/app/package.json index 3a96fc89..5a311218 100644 --- a/app/package.json +++ b/app/package.json @@ -43,6 +43,7 @@ "react-hook-form": "^7.51.4", "react-intersection-observer": "^9.10.2", "react-relay": "^16.2.0", + "react-syntax-highlighter": "^15.5.0", "relay-runtime": "^16.2.0", "tailwindcss": "^3.4.3", "zod": "^3.23.6" @@ -52,6 +53,7 @@ "@types/react": "18.3.1", "@types/react-dom": "18.3.0", "@types/react-relay": "^16.0.6", + "@types/react-syntax-highlighter": "^15.5.13", "@types/relay-runtime": "^14.1.23", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index a55c95a1..c9b1079c 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -47,6 +47,9 @@ dependencies: react-relay: specifier: ^16.2.0 version: 16.2.0(react@18.3.1) + react-syntax-highlighter: + specifier: ^15.5.0 + version: 15.5.0(react@18.3.1) relay-runtime: specifier: ^16.2.0 version: 16.2.0 @@ -70,6 +73,9 @@ devDependencies: '@types/react-relay': specifier: ^16.0.6 version: 16.0.6 + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 '@types/relay-runtime': specifier: ^14.1.23 version: 14.1.23 @@ -1835,6 +1841,12 @@ packages: resolution: {integrity: sha512-PLVe9d7b59sKytbx00KgeGhQG3N176Ezv8YMmsnSz4s0ifDzMWlp/h2wEfQZ0ZNe8e377GY2OW6kovUe3Rnd0g==} dev: false + /@types/hast@2.3.10: + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + dependencies: + '@types/unist': 2.0.10 + dev: false + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -1866,6 +1878,12 @@ packages: '@types/relay-runtime': 14.1.23 dev: true + /@types/react-syntax-highlighter@15.5.13: + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + dependencies: + '@types/react': 18.3.1 + dev: true + /@types/react@18.3.1: resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==} dependencies: @@ -1881,6 +1899,10 @@ packages: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true + /@types/unist@2.0.10: + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + dev: false + /@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0)(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -2437,6 +2459,18 @@ packages: supports-color: 7.2.0 dev: true + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: false + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2494,6 +2528,10 @@ packages: color-string: 1.9.1 dev: false + /comma-separated-tokens@1.0.8: + resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + dev: false + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -3461,6 +3499,12 @@ packages: dependencies: reusify: 1.0.4 + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + dependencies: + format: 0.2.2 + dev: false + /fbjs-css-vars@1.0.2: resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} dev: false @@ -3536,6 +3580,11 @@ packages: signal-exit: 4.1.0 dev: true + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true @@ -3814,6 +3863,24 @@ packages: function-bind: 1.1.2 dev: true + /hast-util-parse-selector@2.2.5: + resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + dev: false + + /hastscript@6.0.0: + resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + dependencies: + '@types/hast': 2.3.10 + comma-separated-tokens: 1.0.8 + hast-util-parse-selector: 2.2.5 + property-information: 5.6.0 + space-separated-tokens: 1.1.5 + dev: false + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3889,6 +3956,17 @@ packages: loose-envify: 1.4.0 dev: false + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: false + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -3960,6 +4038,10 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: false + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -4000,6 +4082,10 @@ packages: dependencies: is-extglob: 2.1.1 + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: false + /is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -4241,6 +4327,13 @@ packages: dependencies: js-tokens: 4.0.0 + /lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + dev: false + /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} @@ -4584,6 +4677,17 @@ packages: callsites: 3.1.0 dev: true + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4744,6 +4848,16 @@ packages: tslib: 2.6.2 dev: true + /prismjs@1.27.0: + resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} + engines: {node: '>=6'} + dev: false + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: false + /promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} dependencies: @@ -4758,6 +4872,12 @@ packages: react-is: 16.13.1 dev: true + /property-information@5.6.0: + resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + dependencies: + xtend: 4.0.2 + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -4928,6 +5048,19 @@ packages: react: 18.3.1 dev: false + /react-syntax-highlighter@15.5.0(react@18.3.1): + resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} + peerDependencies: + react: '>= 0.14.0' + dependencies: + '@babel/runtime': 7.24.4 + highlight.js: 10.7.3 + lowlight: 1.20.0 + prismjs: 1.29.0 + react: 18.3.1 + refractor: 3.6.0 + dev: false + /react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -4958,6 +5091,14 @@ packages: which-builtin-type: 1.1.3 dev: true + /refractor@3.6.0: + resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + dependencies: + hastscript: 6.0.0 + parse-entities: 2.0.0 + prismjs: 1.27.0 + dev: false + /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -5170,6 +5311,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + /space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5709,6 +5854,11 @@ packages: /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true diff --git a/app/src/components/interfaces/dashboard/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/EncodeDialog.tsx new file mode 100644 index 00000000..5ccac359 --- /dev/null +++ b/app/src/components/interfaces/dashboard/EncodeDialog.tsx @@ -0,0 +1,88 @@ +import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragment.graphql' + +import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' +import { IconX } from '@tabler/icons-react' +import { graphql, useFragment } from 'react-relay' + +import CodeBlock from '@/components/ui/code-block/CodeBlock' +import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' + +const FRAGMENT = graphql` + fragment EncodeDialogFragment on Encode { + command + output + recipe { + name + } + file { + path_info { + name + } + } + ...EncodeStatusBadgeFragment + } +` + +type EncodeDialogProps = React.PropsWithChildren & { + $key: EncodeDialogFragment$key +} + +const EncodeDialog: React.FC = ({ children, $key }) => { + const data = useFragment(FRAGMENT, $key) + + return ( + + {children} + + + {({ close }) => ( + + +
+
+ {data.file.path_info.name} + + + + {data.recipe.name} +
+ +
+ +
+
+
+ + +
+ {data.command && ( + + Command + + + {data.command} + + + )} + + {data.output && ( + + Logs + + + {data.output} + + + )} +
+
+
+ )} +
+
+ ) +} + +export default EncodeDialog diff --git a/app/src/components/tables/encoded/EncodedTable.tsx b/app/src/components/tables/encoded/EncodedTable.tsx index 8e56e7e3..593a62a1 100644 --- a/app/src/components/tables/encoded/EncodedTable.tsx +++ b/app/src/components/tables/encoded/EncodedTable.tsx @@ -1,10 +1,11 @@ import type { EncodedTableFragment$key } from '@/__generated__/EncodedTableFragment.graphql' import type { EncodedTableRefetchQuery } from '@/__generated__/EncodedTableRefetchQuery.graphql' -import { Button, Table, Typography } from '@giantnodes/react' +import { Button, Link, Table } from '@giantnodes/react' import React from 'react' import { graphql, usePaginationFragment } from 'react-relay' +import EncodeDialog from '@/components/interfaces/dashboard/EncodeDialog' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` @@ -27,6 +28,7 @@ const FRAGMENT = graphql` } } ...EncodeBadgesFragment + ...EncodeDialogFragment } } pageInfo { @@ -60,7 +62,11 @@ const EncodedTable: React.FC = ({ $key }) => { {(item) => ( - {item.node.file.path_info.name} + + + diff --git a/app/src/components/ui/code-block/CodeBlock.tsx b/app/src/components/ui/code-block/CodeBlock.tsx new file mode 100644 index 00000000..d7f5e694 --- /dev/null +++ b/app/src/components/ui/code-block/CodeBlock.tsx @@ -0,0 +1,22 @@ +import type { SyntaxHighlighterProps } from 'react-syntax-highlighter' + +import SyntaxHighlighter from 'react-syntax-highlighter' +import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs' + +type CodeBlockProps = Pick & { + children: string | string[] +} + +const CodeBlock: React.FC = ({ children, ...rest }) => ( + + {children} + +) + +export default CodeBlock diff --git a/app/src/components/ui/encode-badges/EncodeBadges.tsx b/app/src/components/ui/encode-badges/EncodeBadges.tsx index e85c4515..94cb8340 100644 --- a/app/src/components/ui/encode-badges/EncodeBadges.tsx +++ b/app/src/components/ui/encode-badges/EncodeBadges.tsx @@ -1,4 +1,4 @@ -import type { EncodeBadgesFragment$key, EncodeStatus } from '@/__generated__/EncodeBadgesFragment.graphql' +import type { EncodeBadgesFragment$key } from '@/__generated__/EncodeBadgesFragment.graphql' import type { ChipProps } from '@giantnodes/react' import { Chip } from '@giantnodes/react' @@ -8,6 +8,8 @@ import { filesize } from 'filesize' import React from 'react' import { graphql, useFragment } from 'react-relay' +import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' + type EncodeBadgesProps = Omit & { $key: EncodeBadgesFragment$key } @@ -17,6 +19,7 @@ const FRAGMENT = graphql` status percent started_at + failure_reason failed_at cancelled_at completed_at @@ -30,6 +33,7 @@ const FRAGMENT = graphql` size probed_at } + ...EncodeStatusBadgeFragment } ` @@ -42,34 +46,6 @@ const EncodeBadges: React.FC = ({ $key, size }) => { maximumFractionDigits: 2, }).format(value) - const getStatusColour = (status: EncodeStatus) => { - switch (status) { - case 'SUBMITTED': - return 'info' - - case 'QUEUED': - return 'info' - - case 'ENCODING': - return 'success' - - case 'DEGRADED': - return 'warning' - - case 'COMPLETED': - return 'success' - - case 'CANCELLED': - return 'neutral' - - case 'FAILED': - return 'danger' - - default: - return 'neutral' - } - } - const SizeChip = React.useCallback(() => { const difference = data.snapshots[data.snapshots.length - 1].size - data.snapshots[0].size const increase = Math.abs(difference / data.snapshots[0].size) @@ -113,7 +89,7 @@ const EncodeBadges: React.FC = ({ $key, size }) => { return (
- {data.status.toLowerCase()} + {data.status !== 'COMPLETED' && data.status !== 'CANCELLED' && data.status !== 'FAILED' && ( <> @@ -143,7 +119,7 @@ const EncodeBadges: React.FC = ({ $key, size }) => { {data.status === 'COMPLETED' && ( <> - + {dayjs.duration(dayjs(data.completed_at).diff(data.created_at)).format('H[h] m[m] s[s]')} @@ -152,13 +128,13 @@ const EncodeBadges: React.FC = ({ $key, size }) => { )} {data.status === 'CANCELLED' && ( - + {dayjs.duration(dayjs(data.cancelled_at).diff(data.created_at)).format('H[h] m[m] s[s]')} )} {data.status === 'FAILED' && ( - + {dayjs.duration(dayjs(data.failed_at).diff(data.created_at)).format('H[h] m[m] s[s]')} )} diff --git a/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx b/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx new file mode 100644 index 00000000..f5c2636d --- /dev/null +++ b/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx @@ -0,0 +1,50 @@ +import type { EncodeStatus, EncodeStatusBadgeFragment$key } from '@/__generated__/EncodeStatusBadgeFragment.graphql' + +import { Chip } from '@giantnodes/react' +import { graphql, useFragment } from 'react-relay' + +type EncodeStatusBadgeProps = { + $key: EncodeStatusBadgeFragment$key +} + +const FRAGMENT = graphql` + fragment EncodeStatusBadgeFragment on Encode { + status + } +` + +const EncodeStatusBadge: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + const color = (status: EncodeStatus) => { + switch (status) { + case 'SUBMITTED': + return 'info' + + case 'QUEUED': + return 'info' + + case 'ENCODING': + return 'success' + + case 'DEGRADED': + return 'warning' + + case 'COMPLETED': + return 'success' + + case 'CANCELLED': + return 'neutral' + + case 'FAILED': + return 'danger' + + default: + return 'neutral' + } + } + + return {data.status.toLowerCase()} +} + +export default EncodeStatusBadge diff --git a/app/src/libraries/dayjs/index.ts b/app/src/libraries/dayjs/index.ts index e4ef58e5..61eb3d04 100644 --- a/app/src/libraries/dayjs/index.ts +++ b/app/src/libraries/dayjs/index.ts @@ -1,6 +1,8 @@ import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' +import localized from 'dayjs/plugin/localizedFormat' import relative from 'dayjs/plugin/relativeTime' dayjs.extend(duration) +dayjs.extend(localized) dayjs.extend(relative) From 944757e0ce3021ed75bf8393ba15db3bb4be7459 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 16:24:20 +1000 Subject: [PATCH 05/19] feat: add global scroll bar style --- app/src/styles/global.css | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/app/src/styles/global.css b/app/src/styles/global.css index f154d2c6..eff9f771 100644 --- a/app/src/styles/global.css +++ b/app/src/styles/global.css @@ -2,22 +2,21 @@ @tailwind components; @tailwind utilities; -@layer base { - :root { - --color-backdrop: 250 250 250; - --color-background: 255 255 255; - --color-middleground: 250 250 250; - --color-foreground: 212 212 216; - --color-title: 39 39 42; - --color-subtitle: 82 82 91; - } +::-webkit-scrollbar { + width: 20px; +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: theme('colors.partition'); + border-radius: theme('borderRadius.xl'); + border: 5px solid transparent; + background-clip: content-box; +} - :root[class~='dark'] { - --color-backdrop: 18 18 18; - --color-background: 24 24 27; - --color-middleground: 39 39 42; - --color-foreground: 63 63 70; - --color-title: 244 244 245; - --color-subtitle: 161 161 170; - } +::-webkit-scrollbar-thumb:hover { + background-color: theme('colors.foreground'); } From 4a0f23560dd82a47c4c6f37a6c0bd1fd5b460ee0 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 17:25:18 +1000 Subject: [PATCH 06/19] feat: raise event on encode outputs and subscribe via encode dialog --- .../interfaces/dashboard/EncodeDialog.tsx | 21 ++++++++++++++-- .../tables/encoding/EncodingTable.tsx | 10 ++++++-- app/src/components/ui/ScrollAnchor.tsx | 18 +++++++++++++ .../Events/RaiseEncodeOutputtedTopic.cs | 25 +++++++++++++++++++ .../Encodes/Events/EncodeOutputtedEvent.cs | 12 +++++++++ .../src/Domain/Aggregates/Encodes/Encode.cs | 7 ++++++ .../EncodeOutputtedSubscription.cs | 23 +++++++++++++++++ 7 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 app/src/components/ui/ScrollAnchor.tsx create mode 100644 src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs create mode 100644 src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs create mode 100644 src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs diff --git a/app/src/components/interfaces/dashboard/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/EncodeDialog.tsx index 5ccac359..8d40a65e 100644 --- a/app/src/components/interfaces/dashboard/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/EncodeDialog.tsx @@ -2,10 +2,12 @@ import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragm import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' import { IconX } from '@tabler/icons-react' -import { graphql, useFragment } from 'react-relay' +import React from 'react' +import { graphql, useFragment, useSubscription } from 'react-relay' import CodeBlock from '@/components/ui/code-block/CodeBlock' import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' +import ScrollAnchor from '@/components/ui/ScrollAnchor' const FRAGMENT = graphql` fragment EncodeDialogFragment on Encode { @@ -23,6 +25,14 @@ const FRAGMENT = graphql` } ` +const OUTPUTTED_SUBSCRIPTION = graphql` + subscription EncodeDialogOutputtedSubscription { + encode_outputted { + output + } + } +` + type EncodeDialogProps = React.PropsWithChildren & { $key: EncodeDialogFragment$key } @@ -30,6 +40,11 @@ type EncodeDialogProps = React.PropsWithChildren & { const EncodeDialog: React.FC = ({ children, $key }) => { const data = useFragment(FRAGMENT, $key) + useSubscription({ + subscription: OUTPUTTED_SUBSCRIPTION, + variables: {}, + }) + return ( {children} @@ -72,7 +87,9 @@ const EncodeDialog: React.FC = ({ children, $key }) => { Logs - {data.output} + + {data.output} + )} diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index ad4c3f66..e181fe13 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -5,11 +5,12 @@ import type { } from '@/__generated__/EncodingTableFragment.graphql' import type { EncodingTableRefetchQuery } from '@/__generated__/EncodingTableRefetchQuery.graphql' -import { Button, Table, Typography } from '@giantnodes/react' +import { Button, Link, Table } from '@giantnodes/react' import { IconProgressX } from '@tabler/icons-react' import React from 'react' import { graphql, useMutation, usePaginationFragment, useSubscription } from 'react-relay' +import EncodeDialog from '@/components/interfaces/dashboard/EncodeDialog' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` @@ -32,6 +33,7 @@ const FRAGMENT = graphql` } } ...EncodeBadgesFragment + ...EncodeDialogFragment } } pageInfo { @@ -130,7 +132,11 @@ const EncodingTable: React.FC = ({ $key }) => { {(item) => ( - {item.node.file.path_info.name} + + +
diff --git a/app/src/components/ui/ScrollAnchor.tsx b/app/src/components/ui/ScrollAnchor.tsx new file mode 100644 index 00000000..972c08ea --- /dev/null +++ b/app/src/components/ui/ScrollAnchor.tsx @@ -0,0 +1,18 @@ +import React from 'react' + +type ScrollAnchorProps = React.PropsWithChildren + +const ScrollAnchor: React.FC = ({ children }) => { + const ref = React.useRef(null) + + React.useEffect(() => ref.current?.scrollIntoView({ behavior: 'smooth' })) + + return ( + <> + {children} +
+ + ) +} + +export default ScrollAnchor diff --git a/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs new file mode 100644 index 00000000..f9a51fe2 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Components/Encodes/Events/RaiseEncodeOutputtedTopic.cs @@ -0,0 +1,25 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes.Repositories; +using HotChocolate.Subscriptions; +using MassTransit; + +namespace Giantnodes.Service.Dashboard.Application.Components.Encodes.Events; + +public class RaiseEncodeOutputtedTopic : IConsumer +{ + private readonly IEncodeRepository _repository; + private readonly ITopicEventSender _sender; + + public RaiseEncodeOutputtedTopic(IEncodeRepository repository, ITopicEventSender sender) + { + _repository = repository; + _sender = sender; + } + + public async Task Consume(ConsumeContext context) + { + var encode = await _repository.SingleAsync(x => x.Id == context.Message.EncodeId); + + await _sender.SendAsync(nameof(EncodeOutputtedEvent), encode, context.CancellationToken); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs new file mode 100644 index 00000000..df858923 --- /dev/null +++ b/src/Service.Dashboard/src/Application.Contracts/Encodes/Events/EncodeOutputtedEvent.cs @@ -0,0 +1,12 @@ +using Giantnodes.Infrastructure.Domain.Events; + +namespace Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; + +public sealed record EncodeOutputtedEvent : DomainEvent +{ + public required Guid EncodeId { get; init; } + + public required string Output { get; init; } + + public required string FullOutput { get; init; } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs index 7936e128..f9a764c8 100644 --- a/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs +++ b/src/Service.Dashboard/src/Domain/Aggregates/Encodes/Encode.cs @@ -224,6 +224,13 @@ public void AppendOutputLog(string output) Guard.Against.NullOrWhiteSpace(output); Output = string.Join(Environment.NewLine, Output, output); + + DomainEvents.Add(new EncodeOutputtedEvent + { + EncodeId = Id, + Output = output, + FullOutput = Output + }); } /// diff --git a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs new file mode 100644 index 00000000..46221f30 --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Subscriptions/EncodeOutputtedSubscription.cs @@ -0,0 +1,23 @@ +using Giantnodes.Service.Dashboard.Application.Contracts.Encodes.Events; +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes; +using Giantnodes.Service.Dashboard.Persistence.DbContexts; +using Microsoft.EntityFrameworkCore; + +namespace Giantnodes.Service.Dashboard.HttpApi.Resolvers.Encodes.Subscriptions; + +[ExtendObjectType(OperationTypeNames.Subscription)] +public class EncodeOutputtedSubscription +{ + [Subscribe] + [Topic(nameof(EncodeOutputtedEvent))] + [UseSingleOrDefault] + [UseProjection] + [UseFiltering] + [UseSorting] + public IQueryable EncodeOutputted( + [Service] ApplicationDbContext database, + [EventMessage] Encode encode) + { + return database.Encodes.Where(x => x.Id == encode.Id).AsNoTracking(); + } +} \ No newline at end of file From ad31e1fe9e30c942ea730dd4d5e0d4600c916b0b Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Tue, 7 May 2024 18:35:01 +1000 Subject: [PATCH 07/19] feat: add sidebar and split components into separate files --- .../interfaces/dashboard/EncodeDialog.tsx | 105 ----------------- .../dashboard/encode-dialog/EncodeDialog.tsx | 107 ++++++++++++++++++ .../encode-dialog/EncodeDialogCommand.tsx | 23 ++++ .../encode-dialog/EncodeDialogLog.tsx | 49 ++++++++ .../encode-dialog/EncodeDialogScript.tsx | 44 +++++++ .../encode-dialog/EncodeDialogSidebar.tsx | 31 +++++ .../encode-dialog/use-encode-dialog.hook.ts | 30 +++++ .../components/interfaces/dashboard/index.ts | 1 + .../tables/encoded/EncodedTable.tsx | 2 +- .../tables/encoding/EncodingTable.tsx | 2 +- .../components/ui/code-block/CodeBlock.tsx | 4 +- 11 files changed, 289 insertions(+), 109 deletions(-) delete mode 100644 app/src/components/interfaces/dashboard/EncodeDialog.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts create mode 100644 app/src/components/interfaces/dashboard/index.ts diff --git a/app/src/components/interfaces/dashboard/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/EncodeDialog.tsx deleted file mode 100644 index 8d40a65e..00000000 --- a/app/src/components/interfaces/dashboard/EncodeDialog.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragment.graphql' - -import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' -import { IconX } from '@tabler/icons-react' -import React from 'react' -import { graphql, useFragment, useSubscription } from 'react-relay' - -import CodeBlock from '@/components/ui/code-block/CodeBlock' -import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' -import ScrollAnchor from '@/components/ui/ScrollAnchor' - -const FRAGMENT = graphql` - fragment EncodeDialogFragment on Encode { - command - output - recipe { - name - } - file { - path_info { - name - } - } - ...EncodeStatusBadgeFragment - } -` - -const OUTPUTTED_SUBSCRIPTION = graphql` - subscription EncodeDialogOutputtedSubscription { - encode_outputted { - output - } - } -` - -type EncodeDialogProps = React.PropsWithChildren & { - $key: EncodeDialogFragment$key -} - -const EncodeDialog: React.FC = ({ children, $key }) => { - const data = useFragment(FRAGMENT, $key) - - useSubscription({ - subscription: OUTPUTTED_SUBSCRIPTION, - variables: {}, - }) - - return ( - - {children} - - - {({ close }) => ( - - -
-
- {data.file.path_info.name} - - - - {data.recipe.name} -
- -
- -
-
-
- - -
- {data.command && ( - - Command - - - {data.command} - - - )} - - {data.output && ( - - Logs - - - - {data.output} - - - - )} -
-
-
- )} -
-
- ) -} - -export default EncodeDialog diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx new file mode 100644 index 00000000..8fdf68a6 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx @@ -0,0 +1,107 @@ +import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragment.graphql' + +import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' +import { IconX } from '@tabler/icons-react' +import React from 'react' +import { graphql, useFragment, useSubscription } from 'react-relay' + +import EncodeDialogScript from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogScript' +import EncodeDialogSidebar from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar' +import { + EncodeDialogContext, + EncodeDialogPage, + useEncodeDialog, +} from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' +import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' + +const FRAGMENT = graphql` + fragment EncodeDialogFragment on Encode { + recipe { + name + } + file { + path_info { + name + } + } + ...EncodeStatusBadgeFragment + ...EncodeDialogScriptFragment + } +` + +const OUTPUTTED_SUBSCRIPTION = graphql` + subscription EncodeDialogOutputtedSubscription { + encode_outputted { + output + } + } +` + +type EncodeDialogProps = React.PropsWithChildren & { + $key: EncodeDialogFragment$key +} + +const EncodeDialog: React.FC = ({ children, $key }) => { + const data = useFragment(FRAGMENT, $key) + const context = useEncodeDialog({ page: EncodeDialogPage.SCRIPT }) + + useSubscription({ + subscription: OUTPUTTED_SUBSCRIPTION, + variables: {}, + }) + + const content = React.useCallback(() => { + switch (context.page) { + case EncodeDialogPage.SCRIPT: + return + + case EncodeDialogPage.ANALYTICS: + return + + default: + throw new Error(`unexpected value ${context.page} was provided.`) + } + }, [context.page, data]) + + return ( + + {children} + + + {({ close }) => ( + + + + + + +
+
+ {data.file.path_info.name} + + + + {data.recipe.name} +
+ +
+ +
+
+
+ + +
{content()}
+
+
+
+
+ )} +
+
+ ) +} + +export default EncodeDialog diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx new file mode 100644 index 00000000..5ad577e6 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx @@ -0,0 +1,23 @@ +import type { EncodeDialogCommandFragment$key } from '@/__generated__/EncodeDialogCommandFragment.graphql' + +import { graphql, useFragment } from 'react-relay' + +import CodeBlock from '@/components/ui/code-block/CodeBlock' + +const FRAGMENT = graphql` + fragment EncodeDialogCommandFragment on Encode { + command + } +` + +type EncodeDialogCommandProps = { + $key: EncodeDialogCommandFragment$key +} + +const EncodeDialogCommand: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + return {data.command} +} + +export default EncodeDialogCommand diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx new file mode 100644 index 00000000..397aad5e --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx @@ -0,0 +1,49 @@ +import type { EncodeDialogLogFragment$key } from '@/__generated__/EncodeDialogLogFragment.graphql' +import type { EncodeDialogLogSubscription } from '@/__generated__/EncodeDialogLogSubscription.graphql' + +import { graphql, useFragment, useSubscription } from 'react-relay' + +import CodeBlock from '@/components/ui/code-block/CodeBlock' +import ScrollAnchor from '@/components/ui/ScrollAnchor' + +const FRAGMENT = graphql` + fragment EncodeDialogLogFragment on Encode { + id + output + } +` + +const SUBSCRIPTION = graphql` + subscription EncodeDialogLogSubscription($where: EncodeFilterInput) { + encode_outputted(where: $where) { + ...EncodeDialogLogFragment + } + } +` + +type EncodeDialogLogProps = { + $key: EncodeDialogLogFragment$key +} + +const EncodeDialogLog: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + useSubscription({ + subscription: SUBSCRIPTION, + variables: { + where: { + id: { + eq: data.id, + }, + }, + }, + }) + + return ( + + {data.output} + + ) +} + +export default EncodeDialogLog diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx new file mode 100644 index 00000000..3717e0f4 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx @@ -0,0 +1,44 @@ +import type { EncodeDialogScriptFragment$key } from '@/__generated__/EncodeDialogScriptFragment.graphql' + +import { Card } from '@giantnodes/react' +import { graphql, useFragment } from 'react-relay' + +import EncodeDialogCommand from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand' +import EncodeDialogLog from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogLog' + +const FRAGMENT = graphql` + fragment EncodeDialogScriptFragment on Encode { + ...EncodeDialogCommandFragment + ...EncodeDialogLogFragment + } +` + +type EncodeDialogScriptProps = { + $key: EncodeDialogScriptFragment$key +} + +const EncodeDialogScript: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + return ( + <> + + Command + + + + + + + + Logs + + + + + + + ) +} + +export default EncodeDialogScript diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx new file mode 100644 index 00000000..157f7cf6 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx @@ -0,0 +1,31 @@ +import { Navigation } from '@giantnodes/react' +import { IconReportAnalytics, IconScript } from '@tabler/icons-react' + +import { + EncodeDialogPage, + useEncodeDialogContext, +} from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' + +const EncodeDialogSidebar = () => { + const { page, setPage } = useEncodeDialogContext() + + return ( + + + setPage(EncodeDialogPage.SCRIPT)}> + + + + + + setPage(EncodeDialogPage.ANALYTICS)}> + + + + + + + ) +} + +export default EncodeDialogSidebar diff --git a/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts b/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts new file mode 100644 index 00000000..85bdabaa --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts @@ -0,0 +1,30 @@ +import React from 'react' + +import { createContext } from '@/utilities/context' + +export enum EncodeDialogPage { + SCRIPT, + ANALYTICS, +} + +type UseEncodeDialogReturn = ReturnType + +type UseEncodeDialogProps = { + page: EncodeDialogPage +} + +export const useEncodeDialog = (props: UseEncodeDialogProps) => { + const [page, setPage] = React.useState(props.page) + + return { + page, + setPage, + } +} + +export const [EncodeDialogContext, useEncodeDialogContext] = createContext({ + name: 'EncodeDialogContext', + strict: true, + errorMessage: + 'useEncodeDialogContext: `context` is undefined. Seems you forgot to wrap component within ', +}) diff --git a/app/src/components/interfaces/dashboard/index.ts b/app/src/components/interfaces/dashboard/index.ts new file mode 100644 index 00000000..86dccc03 --- /dev/null +++ b/app/src/components/interfaces/dashboard/index.ts @@ -0,0 +1 @@ +export { default as EncodeDialog } from '@/components/interfaces/dashboard/encode-dialog/EncodeDialog' diff --git a/app/src/components/tables/encoded/EncodedTable.tsx b/app/src/components/tables/encoded/EncodedTable.tsx index 593a62a1..5cd8ad6a 100644 --- a/app/src/components/tables/encoded/EncodedTable.tsx +++ b/app/src/components/tables/encoded/EncodedTable.tsx @@ -5,7 +5,7 @@ import { Button, Link, Table } from '@giantnodes/react' import React from 'react' import { graphql, usePaginationFragment } from 'react-relay' -import EncodeDialog from '@/components/interfaces/dashboard/EncodeDialog' +import { EncodeDialog } from '@/components/interfaces/dashboard' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index e181fe13..952b34d2 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -10,7 +10,7 @@ import { IconProgressX } from '@tabler/icons-react' import React from 'react' import { graphql, useMutation, usePaginationFragment, useSubscription } from 'react-relay' -import EncodeDialog from '@/components/interfaces/dashboard/EncodeDialog' +import { EncodeDialog } from '@/components/interfaces/dashboard' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` diff --git a/app/src/components/ui/code-block/CodeBlock.tsx b/app/src/components/ui/code-block/CodeBlock.tsx index d7f5e694..ec6c4c81 100644 --- a/app/src/components/ui/code-block/CodeBlock.tsx +++ b/app/src/components/ui/code-block/CodeBlock.tsx @@ -4,7 +4,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter' import { atomOneDark } from 'react-syntax-highlighter/dist/esm/styles/hljs' type CodeBlockProps = Pick & { - children: string | string[] + children?: string | string[] | null } const CodeBlock: React.FC = ({ children, ...rest }) => ( @@ -15,7 +15,7 @@ const CodeBlock: React.FC = ({ children, ...rest }) => ( style={atomOneDark} {...rest} > - {children} + {children ?? ''} ) From f572f5a25e12b4a9cf4d545d2ebc33ab9c1d0d1e Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Wed, 8 May 2024 17:54:00 +1000 Subject: [PATCH 08/19] refactor: collocate common ui components into separate directories --- .../dashboard/encode-dialog/EncodeDialog.tsx | 18 +++++++-------- .../encode-dialog/EncodeDialogCommand.tsx | 23 ------------------- .../encode-dialog/EncodeDialogSidebar.tsx | 16 ++++++------- .../EncodeScriptPanel.tsx} | 22 +++++++++--------- .../encode-dialog/use-encode-dialog.hook.ts | 10 ++++---- .../widgets/EncodeCommandWidget.tsx | 23 +++++++++++++++++++ .../EncodeOutputWidget.tsx} | 20 ++++++++-------- .../tables/encoded/EncodedTable.tsx | 2 +- .../tables/encoding/EncodingTable.tsx | 2 +- 9 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx rename app/src/components/interfaces/dashboard/encode-dialog/{EncodeDialogScript.tsx => panels/EncodeScriptPanel.tsx} (56%) create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx rename app/src/components/interfaces/dashboard/encode-dialog/{EncodeDialogLog.tsx => widgets/EncodeOutputWidget.tsx} (50%) diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx index 8fdf68a6..b9a314e2 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx @@ -5,11 +5,11 @@ import { IconX } from '@tabler/icons-react' import React from 'react' import { graphql, useFragment, useSubscription } from 'react-relay' -import EncodeDialogScript from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogScript' import EncodeDialogSidebar from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar' +import EncodeDialogScript from '@/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel' import { EncodeDialogContext, - EncodeDialogPage, + EncodeDialogPanel, useEncodeDialog, } from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' @@ -25,7 +25,7 @@ const FRAGMENT = graphql` } } ...EncodeStatusBadgeFragment - ...EncodeDialogScriptFragment + ...EncodeScriptPanelFragment } ` @@ -43,7 +43,7 @@ type EncodeDialogProps = React.PropsWithChildren & { const EncodeDialog: React.FC = ({ children, $key }) => { const data = useFragment(FRAGMENT, $key) - const context = useEncodeDialog({ page: EncodeDialogPage.SCRIPT }) + const context = useEncodeDialog({ panel: EncodeDialogPanel.SCRIPT }) useSubscription({ subscription: OUTPUTTED_SUBSCRIPTION, @@ -51,17 +51,17 @@ const EncodeDialog: React.FC = ({ children, $key }) => { }) const content = React.useCallback(() => { - switch (context.page) { - case EncodeDialogPage.SCRIPT: + switch (context.panel) { + case EncodeDialogPanel.SCRIPT: return - case EncodeDialogPage.ANALYTICS: + case EncodeDialogPanel.ANALYTICS: return default: - throw new Error(`unexpected value ${context.page} was provided.`) + throw new Error(`unexpected panel value ${context.panel} was provided.`) } - }, [context.page, data]) + }, [context.panel, data]) return ( diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx deleted file mode 100644 index 5ad577e6..00000000 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { EncodeDialogCommandFragment$key } from '@/__generated__/EncodeDialogCommandFragment.graphql' - -import { graphql, useFragment } from 'react-relay' - -import CodeBlock from '@/components/ui/code-block/CodeBlock' - -const FRAGMENT = graphql` - fragment EncodeDialogCommandFragment on Encode { - command - } -` - -type EncodeDialogCommandProps = { - $key: EncodeDialogCommandFragment$key -} - -const EncodeDialogCommand: React.FC = ({ $key }) => { - const data = useFragment(FRAGMENT, $key) - - return {data.command} -} - -export default EncodeDialogCommand diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx index 157f7cf6..8cb1c30e 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx @@ -2,25 +2,25 @@ import { Navigation } from '@giantnodes/react' import { IconReportAnalytics, IconScript } from '@tabler/icons-react' import { - EncodeDialogPage, + EncodeDialogPanel, useEncodeDialogContext, } from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' const EncodeDialogSidebar = () => { - const { page, setPage } = useEncodeDialogContext() + const { panel, setPanel } = useEncodeDialogContext() return ( - setPage(EncodeDialogPage.SCRIPT)}> - - + setPanel(EncodeDialogPanel.SCRIPT)}> + + - setPage(EncodeDialogPage.ANALYTICS)}> - - + setPanel(EncodeDialogPanel.ANALYTICS)}> + + diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx similarity index 56% rename from app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx rename to app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx index 3717e0f4..46d0f55c 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogScript.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx @@ -1,23 +1,23 @@ -import type { EncodeDialogScriptFragment$key } from '@/__generated__/EncodeDialogScriptFragment.graphql' +import type { EncodeScriptPanelFragment$key } from '@/__generated__/EncodeScriptPanelFragment.graphql' import { Card } from '@giantnodes/react' import { graphql, useFragment } from 'react-relay' -import EncodeDialogCommand from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogCommand' -import EncodeDialogLog from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogLog' +import EncodeDialogCommand from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget' +import EncodeDialogLog from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget' const FRAGMENT = graphql` - fragment EncodeDialogScriptFragment on Encode { - ...EncodeDialogCommandFragment - ...EncodeDialogLogFragment + fragment EncodeScriptPanelFragment on Encode { + ...EncodeCommandWidgetFragment + ...EncodeOutputWidgetFragment } ` -type EncodeDialogScriptProps = { - $key: EncodeDialogScriptFragment$key +type EncodeScriptPanelProps = { + $key: EncodeScriptPanelFragment$key } -const EncodeDialogScript: React.FC = ({ $key }) => { +const EncodeScriptPanel: React.FC = ({ $key }) => { const data = useFragment(FRAGMENT, $key) return ( @@ -31,7 +31,7 @@ const EncodeDialogScript: React.FC = ({ $key }) => { - Logs + Output @@ -41,4 +41,4 @@ const EncodeDialogScript: React.FC = ({ $key }) => { ) } -export default EncodeDialogScript +export default EncodeScriptPanel diff --git a/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts b/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts index 85bdabaa..8958f539 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts +++ b/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts @@ -2,7 +2,7 @@ import React from 'react' import { createContext } from '@/utilities/context' -export enum EncodeDialogPage { +export enum EncodeDialogPanel { SCRIPT, ANALYTICS, } @@ -10,15 +10,15 @@ export enum EncodeDialogPage { type UseEncodeDialogReturn = ReturnType type UseEncodeDialogProps = { - page: EncodeDialogPage + panel: EncodeDialogPanel } export const useEncodeDialog = (props: UseEncodeDialogProps) => { - const [page, setPage] = React.useState(props.page) + const [panel, setPanel] = React.useState(props.panel) return { - page, - setPage, + panel, + setPanel, } } diff --git a/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx new file mode 100644 index 00000000..f9e78213 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx @@ -0,0 +1,23 @@ +import type { EncodeCommandWidgetFragment$key } from '@/__generated__/EncodeCommandWidgetFragment.graphql' + +import { graphql, useFragment } from 'react-relay' + +import CodeBlock from '@/components/ui/code-block/CodeBlock' + +const FRAGMENT = graphql` + fragment EncodeCommandWidgetFragment on Encode { + command + } +` + +type EncodeCommandWidgetProps = { + $key: EncodeCommandWidgetFragment$key +} + +const EncodeCommandWidget: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + return {data.command} +} + +export default EncodeCommandWidget diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget.tsx similarity index 50% rename from app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx rename to app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget.tsx index 397aad5e..051e2b8f 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogLog.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget.tsx @@ -1,5 +1,5 @@ -import type { EncodeDialogLogFragment$key } from '@/__generated__/EncodeDialogLogFragment.graphql' -import type { EncodeDialogLogSubscription } from '@/__generated__/EncodeDialogLogSubscription.graphql' +import type { EncodeOutputWidgetFragment$key } from '@/__generated__/EncodeOutputWidgetFragment.graphql' +import type { EncodeOutputWidgetSubscription } from '@/__generated__/EncodeOutputWidgetSubscription.graphql' import { graphql, useFragment, useSubscription } from 'react-relay' @@ -7,28 +7,28 @@ import CodeBlock from '@/components/ui/code-block/CodeBlock' import ScrollAnchor from '@/components/ui/ScrollAnchor' const FRAGMENT = graphql` - fragment EncodeDialogLogFragment on Encode { + fragment EncodeOutputWidgetFragment on Encode { id output } ` const SUBSCRIPTION = graphql` - subscription EncodeDialogLogSubscription($where: EncodeFilterInput) { + subscription EncodeOutputWidgetSubscription($where: EncodeFilterInput) { encode_outputted(where: $where) { - ...EncodeDialogLogFragment + ...EncodeOutputWidgetFragment } } ` -type EncodeDialogLogProps = { - $key: EncodeDialogLogFragment$key +type EncodeOutputWidgetProps = { + $key: EncodeOutputWidgetFragment$key } -const EncodeDialogLog: React.FC = ({ $key }) => { +const EncodeOutputWidget: React.FC = ({ $key }) => { const data = useFragment(FRAGMENT, $key) - useSubscription({ + useSubscription({ subscription: SUBSCRIPTION, variables: { where: { @@ -46,4 +46,4 @@ const EncodeDialogLog: React.FC = ({ $key }) => { ) } -export default EncodeDialogLog +export default EncodeOutputWidget diff --git a/app/src/components/tables/encoded/EncodedTable.tsx b/app/src/components/tables/encoded/EncodedTable.tsx index 5cd8ad6a..588dedd6 100644 --- a/app/src/components/tables/encoded/EncodedTable.tsx +++ b/app/src/components/tables/encoded/EncodedTable.tsx @@ -63,7 +63,7 @@ const EncodedTable: React.FC = ({ $key }) => { - diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index 952b34d2..68cc8fc2 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -133,7 +133,7 @@ const EncodingTable: React.FC = ({ $key }) => { - From 97a5606933b1ec18c1d4521d388b99bd3cb49f56 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 11:37:08 +1000 Subject: [PATCH 09/19] feat: add blank encode analytics panel --- app/package.json | 2 +- app/pnpm-lock.yaml | 14 +++++++------- .../dashboard/encode-dialog/EncodeDialog.tsx | 5 +++-- .../encode-dialog/EncodeDialogSidebar.tsx | 12 ++++++------ .../encode-dialog/panels/EncodeAnalyticsPanel.tsx | 5 +++++ 5 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx diff --git a/app/package.json b/app/package.json index 5a311218..0ff22381 100644 --- a/app/package.json +++ b/app/package.json @@ -29,7 +29,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@giantnodes/react": "1.0.0-canary.14", + "@giantnodes/react": "1.0.0-canary.16", "@hookform/resolvers": "^3.3.4", "@tabler/icons-react": "^3.3.0", "clsx": "^2.1.1", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index c9b1079c..b0d7b527 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@giantnodes/react': - specifier: 1.0.0-canary.14 - version: 1.0.0-canary.14(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3) + specifier: 1.0.0-canary.16 + version: 1.0.0-canary.16(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3) '@hookform/resolvers': specifier: ^3.3.4 version: 3.3.4(react-hook-form@7.51.4) @@ -237,15 +237,15 @@ packages: tslib: 2.6.2 dev: false - /@giantnodes/react@1.0.0-canary.14(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3): - resolution: {integrity: sha512-tR2MMl1nOvZrS9jEwDm86H/+15SH6wb7m1gZrbRuRQkRmmgbzTkMecc9FfadEMa5ldjdHYBMqfgUomucAXXIGA==} + /@giantnodes/react@1.0.0-canary.16(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3): + resolution: {integrity: sha512-Aq6cjab2OAan3Tb1mLgDxLFz5UYyG/L/77T2lkIV2bttsrRana/DHF29KfZpoNmGEfM5N1gV8LgAuVazyJtP8g==} engines: {node: '>=16.x'} peerDependencies: react: '>=18' react-dom: '>=18' tailwindcss: '>=3' dependencies: - '@giantnodes/theme': 1.0.0-canary.14(tailwindcss@3.4.3) + '@giantnodes/theme': 1.0.0-canary.16(tailwindcss@3.4.3) '@react-aria/utils': 3.24.0(react@18.3.1) clsx: 2.1.1 react: 18.3.1 @@ -256,8 +256,8 @@ packages: tailwindcss-react-aria-components: 1.1.2(tailwindcss@3.4.3) dev: false - /@giantnodes/theme@1.0.0-canary.14(tailwindcss@3.4.3): - resolution: {integrity: sha512-5cYeyX8VMV8oST0uClTxx/WMNaeYYVboKWdb0KDLkI/VytOF3E9jLYoHKW31swdpk9omQmq2W1eMSQ70WFtF8g==} + /@giantnodes/theme@1.0.0-canary.16(tailwindcss@3.4.3): + resolution: {integrity: sha512-e6k+Qx0c3Q0ASQOeIYccyZ4YIIsNdqfLdINOjjI056k6rMS6e/IbUvM1BHjdquSfiS7He9xCRFvllEbjMmsTHg==} engines: {node: '>=16.x'} peerDependencies: tailwindcss: '>=3' diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx index b9a314e2..e47b90bc 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx @@ -6,6 +6,7 @@ import React from 'react' import { graphql, useFragment, useSubscription } from 'react-relay' import EncodeDialogSidebar from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar' +import EncodeAnalyticsPanel from '@/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel' import EncodeDialogScript from '@/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel' import { EncodeDialogContext, @@ -56,7 +57,7 @@ const EncodeDialog: React.FC = ({ children, $key }) => { return case EncodeDialogPanel.ANALYTICS: - return + return default: throw new Error(`unexpected panel value ${context.panel} was provided.`) @@ -73,7 +74,7 @@ const EncodeDialog: React.FC = ({ children, $key }) => { - +
diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx index 8cb1c30e..73a63381 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx @@ -12,16 +12,16 @@ const EncodeDialogSidebar = () => { return ( - setPanel(EncodeDialogPanel.SCRIPT)}> - + + setPanel(EncodeDialogPanel.SCRIPT)}> - + - setPanel(EncodeDialogPanel.ANALYTICS)}> - + + setPanel(EncodeDialogPanel.ANALYTICS)}> - + diff --git a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx new file mode 100644 index 00000000..f7cdee9d --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx @@ -0,0 +1,5 @@ +type EncodeAnalyticsPanelProps = {} + +const EncodeAnalyticsPanel: React.FC = () => <>Analytics + +export default EncodeAnalyticsPanel From d5f813eb50230e2befa5312d8e633240bc694b85 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 13:18:20 +1000 Subject: [PATCH 10/19] feat: add encode size comparison widget --- app/package.json | 1 + app/pnpm-lock.yaml | 223 +++++++++++++++++- .../dashboard/encode-dialog/EncodeDialog.tsx | 12 +- .../panels/EncodeAnalyticsPanel.tsx | 31 ++- .../widgets/EncodeSizeWidget.tsx | 94 ++++++++ 5 files changed, 354 insertions(+), 7 deletions(-) create mode 100644 app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx diff --git a/app/package.json b/app/package.json index 0ff22381..106ea15a 100644 --- a/app/package.json +++ b/app/package.json @@ -44,6 +44,7 @@ "react-intersection-observer": "^9.10.2", "react-relay": "^16.2.0", "react-syntax-highlighter": "^15.5.0", + "recharts": "^2.12.7", "relay-runtime": "^16.2.0", "tailwindcss": "^3.4.3", "zod": "^3.23.6" diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index b0d7b527..c48c9dc2 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -50,6 +50,9 @@ dependencies: react-syntax-highlighter: specifier: ^15.5.0 version: 15.5.0(react@18.3.1) + recharts: + specifier: ^2.12.7 + version: 2.12.7(react-dom@18.3.1)(react@18.3.1) relay-runtime: specifier: ^16.2.0 version: 16.2.0 @@ -1841,6 +1844,48 @@ packages: resolution: {integrity: sha512-PLVe9d7b59sKytbx00KgeGhQG3N176Ezv8YMmsnSz4s0ifDzMWlp/h2wEfQZ0ZNe8e377GY2OW6kovUe3Rnd0g==} dev: false + /@types/d3-array@3.2.1: + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + dev: false + + /@types/d3-color@3.1.3: + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + dev: false + + /@types/d3-ease@3.0.2: + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + dev: false + + /@types/d3-interpolate@3.0.4: + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + dependencies: + '@types/d3-color': 3.1.3 + dev: false + + /@types/d3-path@3.1.0: + resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} + dev: false + + /@types/d3-scale@4.0.8: + resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} + dependencies: + '@types/d3-time': 3.0.3 + dev: false + + /@types/d3-shape@3.1.6: + resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + dependencies: + '@types/d3-path': 3.1.0 + dev: false + + /@types/d3-time@3.0.3: + resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} + dev: false + + /@types/d3-timer@3.0.2: + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + dev: false + /@types/hast@2.3.10: resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} dependencies: @@ -2567,7 +2612,77 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - dev: true + + /d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -2627,6 +2742,10 @@ packages: ms: 2.1.2 dev: true + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -2721,6 +2840,13 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.24.4 + csstype: 3.1.2 + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -3442,6 +3568,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -3476,6 +3606,11 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + dev: false + /fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -3941,6 +4076,11 @@ packages: side-channel: 1.0.4 dev: true + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /intl-messageformat@10.5.11: resolution: {integrity: sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==} dependencies: @@ -4321,6 +4461,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -4870,7 +5014,6 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - dev: true /property-information@5.6.0: resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} @@ -5000,7 +5143,6 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: true /react-relay@16.2.0(react@18.3.1): resolution: {integrity: sha512-f/HtC4whyYmK6/WUeOVakXRoBkV+JEgoSeBHXfIC2U6AuH14NrKXnFicX65LksfzgD1OUfYF6IqGQ4MvO52lTQ==} @@ -5017,6 +5159,19 @@ packages: - encoding dev: false + /react-smooth@4.0.1(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + fast-equals: 5.0.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1) + dev: false + /react-stately@3.31.0(react@18.3.1): resolution: {integrity: sha512-G6y7t6qpP3LU4mLM2RlRTgdW5eiZrR2yB0XZbLo8qVplazxyRzlDJRBdE8OBTpw2SO1q5Auub3NOTH3vH0qCHg==} peerDependencies: @@ -5061,6 +5216,20 @@ packages: refractor: 3.6.0 dev: false + /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.24.4 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -5079,6 +5248,31 @@ packages: dependencies: picomatch: 2.3.1 + /recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + dependencies: + decimal.js-light: 2.5.1 + dev: false + + /recharts@2.12.7(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.21 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 16.13.1 + react-smooth: 4.0.1(react-dom@18.3.1)(react@18.3.1) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + dev: false + /reflect.getprototypeof@1.0.4: resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} engines: {node: '>= 0.4'} @@ -5558,6 +5752,10 @@ packages: dependencies: any-promise: 1.3.0 + /tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + dev: false + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -5757,6 +5955,25 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.8 + '@types/d3-shape': 3.1.6 + '@types/d3-time': 3.0.3 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx index e47b90bc..e72dc2b3 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx @@ -27,6 +27,7 @@ const FRAGMENT = graphql` } ...EncodeStatusBadgeFragment ...EncodeScriptPanelFragment + ...EncodeAnalyticsPanelFragment } ` @@ -57,7 +58,7 @@ const EncodeDialog: React.FC = ({ children, $key }) => { return case EncodeDialogPanel.ANALYTICS: - return + return default: throw new Error(`unexpected panel value ${context.panel} was provided.`) @@ -86,7 +87,14 @@ const EncodeDialog: React.FC = ({ children, $key }) => {
-
diff --git a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx index f7cdee9d..7365d0fb 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx @@ -1,5 +1,32 @@ -type EncodeAnalyticsPanelProps = {} +import type { EncodeAnalyticsPanelFragment$key } from '@/__generated__/EncodeAnalyticsPanelFragment.graphql' -const EncodeAnalyticsPanel: React.FC = () => <>Analytics +import { Card } from '@giantnodes/react' +import { graphql, useFragment } from 'react-relay' + +import EncodeSizeWidget from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget' + +const FRAGMENT = graphql` + fragment EncodeAnalyticsPanelFragment on Encode { + ...EncodeSizeWidgetFragment + } +` + +type EncodeAnalyticsPanelProps = { + $key: EncodeAnalyticsPanelFragment$key +} + +const EncodeAnalyticsPanel: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + return ( + + Size + + + + + + ) +} export default EncodeAnalyticsPanel diff --git a/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx new file mode 100644 index 00000000..27eff110 --- /dev/null +++ b/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx @@ -0,0 +1,94 @@ +import type { EncodeSizeWidgetFragment$key } from '@/__generated__/EncodeSizeWidgetFragment.graphql' +import type { DefaultTooltipContent } from 'recharts' + +import { Card, Typography } from '@giantnodes/react' +import { filesize } from 'filesize' +import React from 'react' +import { graphql, useFragment } from 'react-relay' +import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts' + +const FRAGMENT = graphql` + fragment EncodeSizeWidgetFragment on Encode { + snapshots { + size + created_at + } + } +` + +type EncodeSizeWidgetProps = { + $key: EncodeSizeWidgetFragment$key +} + +const EncodeSizeWidget: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + const bars = React.useMemo(() => { + const snapshots = data.snapshots.toSorted((a, b) => a.created_at - b.created_at) + + const output = [ + { + name: 'Original', + size: snapshots.at(0)?.size, + fill: 'hsl(var(--twc-info) / 0.2)', + stroke: 'hsl(var(--twc-info))', + }, + { + name: 'New', + size: snapshots.length === 1 ? 0 : snapshots.at(snapshots.length - 1)?.size, + fill: 'hsl(var(--twc-brand) / 0.2)', + stroke: 'hsl(var(--twc-brand))', + }, + ] + + return output + }, [data]) + + const TooltipContent: typeof DefaultTooltipContent = React.useCallback((props) => { + const value = props.payload?.at(0)?.value + + return ( + + + {props.label} + + + {value !== undefined && ( + + + {filesize(value, { base: 2 })} + + + )} + + ) + }, []) + + return ( + + + + + + + filesize(tick, { base: 2 })} + type="number" + /> + + + + ) +} + +export default EncodeSizeWidget From fea9517074f143c646382e9dd8cd7353f6f2d11b Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 13:29:07 +1000 Subject: [PATCH 11/19] fix: use @giantnodes/react v1.0.0-canary.16 components --- app/src/app/(dashboard)/layout.tsx | 8 ++-- app/src/app/(dashboard)/recipes/page.tsx | 2 +- .../[slug]/explore/[[...path]]/page.tsx | 10 ++--- .../library/[slug]/explore/layout.tsx | 2 +- .../app/(libraries)/library/[slug]/page.tsx | 2 +- .../layouts/dashboard/navbar/Navbar.tsx | 2 +- .../dashboard/sidebar/SettingsSidebar.tsx | 37 +++++++---------- .../layouts/dashboard/sidebar/Sidebar.tsx | 14 +++---- .../sidebar/SidebarLibrarySegment.tsx | 17 ++++---- .../layouts/library/navbar/Navbar.tsx | 2 +- .../library/sidebar/SettingSidebar.tsx | 15 +++---- .../layouts/library/sidebar/Sidebar.tsx | 41 ++++++++----------- 12 files changed, 66 insertions(+), 86 deletions(-) diff --git a/app/src/app/(dashboard)/layout.tsx b/app/src/app/(dashboard)/layout.tsx index a65a3546..35aa9b1a 100644 --- a/app/src/app/(dashboard)/layout.tsx +++ b/app/src/app/(dashboard)/layout.tsx @@ -2,9 +2,11 @@ import React from 'react' import DefaultLayout from '@/components/layouts/dashboard/DashboardLayout' -type DashboardLayoutProps = React.PropsWithChildren +type DashboardSegmentLayoutProps = React.PropsWithChildren -const DashboardLayout: React.FC = ({ children }) => {children} +const DashboardSegmentLayout: React.FC = ({ children }) => ( + {children} +) export const dynamic = 'force-dynamic' -export default DashboardLayout +export default DashboardSegmentLayout diff --git a/app/src/app/(dashboard)/recipes/page.tsx b/app/src/app/(dashboard)/recipes/page.tsx index a0a2b852..05753694 100644 --- a/app/src/app/(dashboard)/recipes/page.tsx +++ b/app/src/app/(dashboard)/recipes/page.tsx @@ -21,7 +21,7 @@ const RecipeListPage: React.FC = () => { }) return ( -
+
diff --git a/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx b/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx index d8489ce6..b6340485 100644 --- a/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx +++ b/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx @@ -82,9 +82,9 @@ const LibraryExplorePage: React.FC = ({ params }) => { const context = useExplore({ directory: query.file_system_directory.id }) return ( -
+
-
+
@@ -109,10 +109,10 @@ const LibraryExplorePage: React.FC = ({ params }) => {
-
- +
+ - Resolution + Resolution diff --git a/app/src/app/(libraries)/library/[slug]/explore/layout.tsx b/app/src/app/(libraries)/library/[slug]/explore/layout.tsx index 2586b0af..53c29d89 100644 --- a/app/src/app/(libraries)/library/[slug]/explore/layout.tsx +++ b/app/src/app/(libraries)/library/[slug]/explore/layout.tsx @@ -1,7 +1,7 @@ type LibrarySlugExplorePageLayoutProps = React.PropsWithChildren const LibrarySlugExplorePageLayout: React.FC = ({ children }) => ( -
{children}
+
{children}
) export default LibrarySlugExplorePageLayout diff --git a/app/src/app/(libraries)/library/[slug]/page.tsx b/app/src/app/(libraries)/library/[slug]/page.tsx index f64347c0..8ca6b581 100644 --- a/app/src/app/(libraries)/library/[slug]/page.tsx +++ b/app/src/app/(libraries)/library/[slug]/page.tsx @@ -33,7 +33,7 @@ const LibraryDashboard = () => { }) return ( - + Tasks diff --git a/app/src/components/layouts/dashboard/navbar/Navbar.tsx b/app/src/components/layouts/dashboard/navbar/Navbar.tsx index e38bb23e..ce858bdd 100644 --- a/app/src/components/layouts/dashboard/navbar/Navbar.tsx +++ b/app/src/components/layouts/dashboard/navbar/Navbar.tsx @@ -4,7 +4,7 @@ import { Input, Navigation } from '@giantnodes/react' import { IconBell, IconSearch } from '@tabler/icons-react' const Navbar: React.FC = (props) => ( - + diff --git a/app/src/components/layouts/dashboard/sidebar/SettingsSidebar.tsx b/app/src/components/layouts/dashboard/sidebar/SettingsSidebar.tsx index 37097b20..6a5e6c06 100644 --- a/app/src/components/layouts/dashboard/sidebar/SettingsSidebar.tsx +++ b/app/src/components/layouts/dashboard/sidebar/SettingsSidebar.tsx @@ -2,7 +2,6 @@ import { Navigation } from '@giantnodes/react' import { IconHomeCog, IconServerCog, IconUserCog } from '@tabler/icons-react' -import Link from 'next/link' import { usePathname } from 'next/navigation' const SettingSidebar: React.FC = () => { @@ -11,36 +10,30 @@ const SettingSidebar: React.FC = () => { const route = router.split('/')[2] return ( - + Settings - - - - General - - + + + General + - - - - - Preferences - - + + + + Preferences + - - - - - Encoder - - + + + + Encoder + diff --git a/app/src/components/layouts/dashboard/sidebar/Sidebar.tsx b/app/src/components/layouts/dashboard/sidebar/Sidebar.tsx index 12cdfd45..6587e09b 100644 --- a/app/src/components/layouts/dashboard/sidebar/Sidebar.tsx +++ b/app/src/components/layouts/dashboard/sidebar/Sidebar.tsx @@ -35,20 +35,20 @@ const Sidebar: React.FC = ({ $key, ...rest }) => { const route = router.split('/')[1] return ( - + giantnodes logo - - + + Dashboard - - + + Recipes @@ -68,8 +68,8 @@ const Sidebar: React.FC = ({ $key, ...rest }) => { - - + + Settings diff --git a/app/src/components/layouts/dashboard/sidebar/SidebarLibrarySegment.tsx b/app/src/components/layouts/dashboard/sidebar/SidebarLibrarySegment.tsx index 74157925..89dd127c 100644 --- a/app/src/components/layouts/dashboard/sidebar/SidebarLibrarySegment.tsx +++ b/app/src/components/layouts/dashboard/sidebar/SidebarLibrarySegment.tsx @@ -9,7 +9,6 @@ import type { AvatarProps } from '@giantnodes/react' import { Avatar, Navigation } from '@giantnodes/react' import { IconFolderCheck, IconFolderExclamation, IconFolderQuestion, IconFolderX } from '@tabler/icons-react' -import Link from 'next/link' import { graphql, usePaginationFragment } from 'react-relay' const FRAGMENT = graphql` @@ -78,16 +77,14 @@ const SidebarLibrarySegment: React.FC = ({ $key }) = <> {data?.libraries?.edges?.map((edge) => ( - - - - - - + + + + + - {edge.node.name} - - + {edge.node.name} + ))} diff --git a/app/src/components/layouts/library/navbar/Navbar.tsx b/app/src/components/layouts/library/navbar/Navbar.tsx index e11256a2..385edb7d 100644 --- a/app/src/components/layouts/library/navbar/Navbar.tsx +++ b/app/src/components/layouts/library/navbar/Navbar.tsx @@ -6,7 +6,7 @@ import { Input, Navigation } from '@giantnodes/react' import { IconBell, IconSearch } from '@tabler/icons-react' const Navbar: React.FC = (props) => ( - + diff --git a/app/src/components/layouts/library/sidebar/SettingSidebar.tsx b/app/src/components/layouts/library/sidebar/SettingSidebar.tsx index d8457ea1..f3e34554 100644 --- a/app/src/components/layouts/library/sidebar/SettingSidebar.tsx +++ b/app/src/components/layouts/library/sidebar/SettingSidebar.tsx @@ -2,7 +2,6 @@ import { Navigation } from '@giantnodes/react' import { IconHomeCog } from '@tabler/icons-react' -import Link from 'next/link' import { usePathname } from 'next/navigation' import { useLibraryContext } from '@/app/(libraries)/library/[slug]/use-library.hook' @@ -14,19 +13,17 @@ const SettingSidebar: React.FC = () => { const route = router.split('/')[4] return ( - + Settings - - - - - General - - + + + + General + diff --git a/app/src/components/layouts/library/sidebar/Sidebar.tsx b/app/src/components/layouts/library/sidebar/Sidebar.tsx index b833c325..e31cf034 100644 --- a/app/src/components/layouts/library/sidebar/Sidebar.tsx +++ b/app/src/components/layouts/library/sidebar/Sidebar.tsx @@ -5,7 +5,6 @@ import type { NavigationProps } from '@giantnodes/react' import { Navigation } from '@giantnodes/react' import { IconFolders, IconGauge, IconHome, IconSettings } from '@tabler/icons-react' import Image from 'next/image' -import Link from 'next/link' import { usePathname } from 'next/navigation' import { useLibraryContext } from '@/app/(libraries)/library/[slug]/use-library.hook' @@ -17,46 +16,38 @@ const Sidebar: React.FC = ({ ...rest }) => { const route = router.split('/')[3] return ( - + giantnodes logo - - - - - + + + - - - - - - + + + + - - - - - - + + + + - - - - - - + + + + From 83a92c4fbf4b4d24e760a46c7eb5ffee526440ee Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 14:41:39 +1000 Subject: [PATCH 12/19] feat: use intercepting routes to display dialog --- .../@dialog/(.)encode/[id]/page.tsx | 46 +++++++++++++++++++ app/src/app/(dashboard)/@dialog/default.tsx | 3 ++ app/src/app/(dashboard)/encode/[id]/page.tsx | 46 +++++++++++++++++++ app/src/app/(dashboard)/layout.tsx | 11 +++-- .../dashboard/encode-dialog/EncodeDialog.tsx | 9 ++-- .../tables/encoded/EncodedTable.tsx | 8 +--- .../Encodes/Queries/EncodeFindOne.cs | 18 ++++++++ .../Filters/FileSystemEntryFilterType.cs | 14 ++++++ 8 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx create mode 100644 app/src/app/(dashboard)/@dialog/default.tsx create mode 100644 app/src/app/(dashboard)/encode/[id]/page.tsx create mode 100644 src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs create mode 100644 src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs diff --git a/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx new file mode 100644 index 00000000..cce9daeb --- /dev/null +++ b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx @@ -0,0 +1,46 @@ +'use client' + +import type { page_EncodedDialog_Query } from '@/__generated__/page_EncodedDialog_Query.graphql' + +import { notFound, useRouter } from 'next/navigation' +import { graphql, useLazyLoadQuery } from 'react-relay' + +import { EncodeDialog } from '@/components/interfaces/dashboard' + +const QUERY = graphql` + query page_EncodedDialog_Query($where: EncodeFilterInput) { + encode(where: $where) { + ...EncodeDialogFragment + } + } +` + +type EncodePageProps = React.PropsWithChildren & { + params: { + id: string + } +} + +const EncodePage: React.FC = ({ children, params }) => { + const router = useRouter() + + const query = useLazyLoadQuery(QUERY, { + where: { + id: { + eq: decodeURIComponent(params.id), + }, + }, + }) + + if (query.encode == null) { + return notFound() + } + + return ( + + {children} + + ) +} + +export default EncodePage diff --git a/app/src/app/(dashboard)/@dialog/default.tsx b/app/src/app/(dashboard)/@dialog/default.tsx new file mode 100644 index 00000000..064fd6d1 --- /dev/null +++ b/app/src/app/(dashboard)/@dialog/default.tsx @@ -0,0 +1,3 @@ +const Default = () => null + +export default Default diff --git a/app/src/app/(dashboard)/encode/[id]/page.tsx b/app/src/app/(dashboard)/encode/[id]/page.tsx new file mode 100644 index 00000000..8e785933 --- /dev/null +++ b/app/src/app/(dashboard)/encode/[id]/page.tsx @@ -0,0 +1,46 @@ +'use client' + +import type { page_EncodedPage_Query } from '@/__generated__/page_EncodedPage_Query.graphql' + +import { notFound, useRouter } from 'next/navigation' +import { graphql, useLazyLoadQuery } from 'react-relay' + +import { EncodeDialog } from '@/components/interfaces/dashboard' + +const QUERY = graphql` + query page_EncodedPage_Query($where: EncodeFilterInput) { + encode(where: $where) { + ...EncodeDialogFragment + } + } +` + +type EncodePageProps = React.PropsWithChildren & { + params: { + id: string + } +} + +const EncodePage: React.FC = ({ children, params }) => { + const router = useRouter() + + const query = useLazyLoadQuery(QUERY, { + where: { + id: { + eq: decodeURIComponent(params.id), + }, + }, + }) + + if (query.encode == null) { + return notFound() + } + + return ( + + {children} + + ) +} + +export default EncodePage diff --git a/app/src/app/(dashboard)/layout.tsx b/app/src/app/(dashboard)/layout.tsx index 35aa9b1a..b898053e 100644 --- a/app/src/app/(dashboard)/layout.tsx +++ b/app/src/app/(dashboard)/layout.tsx @@ -2,10 +2,15 @@ import React from 'react' import DefaultLayout from '@/components/layouts/dashboard/DashboardLayout' -type DashboardSegmentLayoutProps = React.PropsWithChildren +type DashboardSegmentLayoutProps = React.PropsWithChildren & { + dialog: React.ReactNode +} -const DashboardSegmentLayout: React.FC = ({ children }) => ( - {children} +const DashboardSegmentLayout: React.FC = ({ children, dialog }) => ( + + {children} + {dialog} + ) export const dynamic = 'force-dynamic' diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx index e72dc2b3..113a59ea 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx @@ -1,4 +1,5 @@ import type { EncodeDialogFragment$key } from '@/__generated__/EncodeDialogFragment.graphql' +import type { DialogProps } from '@giantnodes/react' import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' import { IconX } from '@tabler/icons-react' @@ -41,9 +42,9 @@ const OUTPUTTED_SUBSCRIPTION = graphql` type EncodeDialogProps = React.PropsWithChildren & { $key: EncodeDialogFragment$key -} +} & DialogProps -const EncodeDialog: React.FC = ({ children, $key }) => { +const EncodeDialog: React.FC = ({ $key, children, ...rest }) => { const data = useFragment(FRAGMENT, $key) const context = useEncodeDialog({ panel: EncodeDialogPanel.SCRIPT }) @@ -66,7 +67,7 @@ const EncodeDialog: React.FC = ({ children, $key }) => { }, [context.panel, data]) return ( - + {children} @@ -91,8 +92,8 @@ const EncodeDialog: React.FC = ({ children, $key }) => { color="transparent" size="xs" onPress={() => { - context.setPanel(EncodeDialogPanel.SCRIPT) close() + context.setPanel(EncodeDialogPanel.SCRIPT) }} > diff --git a/app/src/components/tables/encoded/EncodedTable.tsx b/app/src/components/tables/encoded/EncodedTable.tsx index 588dedd6..78a24c4e 100644 --- a/app/src/components/tables/encoded/EncodedTable.tsx +++ b/app/src/components/tables/encoded/EncodedTable.tsx @@ -5,7 +5,6 @@ import { Button, Link, Table } from '@giantnodes/react' import React from 'react' import { graphql, usePaginationFragment } from 'react-relay' -import { EncodeDialog } from '@/components/interfaces/dashboard' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` @@ -28,7 +27,6 @@ const FRAGMENT = graphql` } } ...EncodeBadgesFragment - ...EncodeDialogFragment } } pageInfo { @@ -62,11 +60,7 @@ const EncodedTable: React.FC = ({ $key }) => { {(item) => ( - - - + {item.node.file.path_info.name} diff --git a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs new file mode 100644 index 00000000..de40e843 --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs @@ -0,0 +1,18 @@ +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes; +using Giantnodes.Service.Dashboard.Persistence.DbContexts; +using Microsoft.EntityFrameworkCore; + +namespace Giantnodes.Service.Dashboard.HttpApi.Resolvers.Encodes.Queries; + +[ExtendObjectType(OperationTypeNames.Query)] +public class EncodeFindMany +{ + [UsePaging] + [UseProjection] + [UseFiltering] + [UseSorting] + public IQueryable Encodes([Service] ApplicationDbContext database) + { + return database.Encodes.AsNoTracking(); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs new file mode 100644 index 00000000..a55db402 --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs @@ -0,0 +1,14 @@ +using Giantnodes.Service.Dashboard.Domain.Aggregates.Entries; +using HotChocolate.Data.Filters; + +namespace Giantnodes.Service.Dashboard.HttpApi.Types.Entries.Filters; + +public class FileSystemEntryFilterType : FilterInputType +{ + protected override void Configure(IFilterInputTypeDescriptor descriptor) + { + descriptor + .Field(p => p.Id) + .Type(); + } +} \ No newline at end of file From 5447c62e95a8134efcfa4ca887bc1abb25f02aa1 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 18:45:42 +1000 Subject: [PATCH 13/19] feat: add dedicated encode detail page --- .../@dialog/(.)encode/[id]/page.tsx | 2 +- app/src/app/(dashboard)/encode/[id]/page.tsx | 69 ++++++++++++++++--- app/src/app/(dashboard)/recipes/page.tsx | 2 +- .../[slug]/explore/[[...path]]/page.tsx | 15 ++-- .../components/interfaces/dashboard/index.ts | 1 - .../dialog}/EncodeDialog.tsx | 8 +-- .../dialog}/EncodeDialogSidebar.tsx | 5 +- .../dialog}/panels/EncodeAnalyticsPanel.tsx | 10 +-- .../dialog}/panels/EncodeScriptPanel.tsx | 17 +++-- .../dialog}/use-encode-dialog.hook.ts | 0 app/src/components/interfaces/encode/index.ts | 5 ++ .../widgets/EncodeCommandWidget.tsx | 0 .../widgets/EncodeOutputWidget.tsx | 0 .../widgets/EncodeSizeWidget.tsx | 0 .../components/interfaces/explore/index.ts | 1 - .../FileSystemBreadcrumb.tsx} | 12 ++-- .../interfaces/file-system/index.ts | 1 + app/src/components/interfaces/index.ts | 1 - .../tables/encoding/EncodingTable.tsx | 8 +-- 19 files changed, 101 insertions(+), 56 deletions(-) delete mode 100644 app/src/components/interfaces/dashboard/index.ts rename app/src/components/interfaces/{dashboard/encode-dialog => encode/dialog}/EncodeDialog.tsx (89%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode/dialog}/EncodeDialogSidebar.tsx (87%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode/dialog}/panels/EncodeAnalyticsPanel.tsx (71%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode/dialog}/panels/EncodeScriptPanel.tsx (64%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode/dialog}/use-encode-dialog.hook.ts (100%) create mode 100644 app/src/components/interfaces/encode/index.ts rename app/src/components/interfaces/{dashboard/encode-dialog => encode}/widgets/EncodeCommandWidget.tsx (100%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode}/widgets/EncodeOutputWidget.tsx (100%) rename app/src/components/interfaces/{dashboard/encode-dialog => encode}/widgets/EncodeSizeWidget.tsx (100%) rename app/src/components/interfaces/{explore/breadcrumbs/ExploreBreadcrumbs.tsx => file-system/FileSystemBreadcrumb.tsx} (84%) create mode 100644 app/src/components/interfaces/file-system/index.ts delete mode 100644 app/src/components/interfaces/index.ts diff --git a/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx index cce9daeb..509a3ce0 100644 --- a/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx @@ -5,7 +5,7 @@ import type { page_EncodedDialog_Query } from '@/__generated__/page_EncodedDialo import { notFound, useRouter } from 'next/navigation' import { graphql, useLazyLoadQuery } from 'react-relay' -import { EncodeDialog } from '@/components/interfaces/dashboard' +import { EncodeDialog } from '@/components/interfaces/encode' const QUERY = graphql` query page_EncodedDialog_Query($where: EncodeFilterInput) { diff --git a/app/src/app/(dashboard)/encode/[id]/page.tsx b/app/src/app/(dashboard)/encode/[id]/page.tsx index 8e785933..f1e712db 100644 --- a/app/src/app/(dashboard)/encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/encode/[id]/page.tsx @@ -2,15 +2,22 @@ import type { page_EncodedPage_Query } from '@/__generated__/page_EncodedPage_Query.graphql' -import { notFound, useRouter } from 'next/navigation' +import { Card, Typography } from '@giantnodes/react' +import { notFound } from 'next/navigation' import { graphql, useLazyLoadQuery } from 'react-relay' -import { EncodeDialog } from '@/components/interfaces/dashboard' +import { EncodeCommandWidget, EncodeOutputWidget, EncodeSizeWidget } from '@/components/interfaces/encode' +import { FileSystemBreadcrumb } from '@/components/interfaces/file-system' const QUERY = graphql` query page_EncodedPage_Query($where: EncodeFilterInput) { encode(where: $where) { - ...EncodeDialogFragment + file { + ...FileSystemBreadcrumbFragment + } + ...EncodeCommandWidgetFragment + ...EncodeOutputWidgetFragment + ...EncodeSizeWidgetFragment } } ` @@ -21,9 +28,7 @@ type EncodePageProps = React.PropsWithChildren & { } } -const EncodePage: React.FC = ({ children, params }) => { - const router = useRouter() - +const EncodePage: React.FC = ({ params }) => { const query = useLazyLoadQuery(QUERY, { where: { id: { @@ -37,9 +42,55 @@ const EncodePage: React.FC = ({ children, params }) => { } return ( - - {children} - +
+
+
+ + + + + + + + + Size + + + + + + + + + + Command + + + + + + + + + + Output + + + + + + +
+ +
+ + + Tbd + + +
+
+
) } diff --git a/app/src/app/(dashboard)/recipes/page.tsx b/app/src/app/(dashboard)/recipes/page.tsx index 05753694..83b98c16 100644 --- a/app/src/app/(dashboard)/recipes/page.tsx +++ b/app/src/app/(dashboard)/recipes/page.tsx @@ -23,7 +23,7 @@ const RecipeListPage: React.FC = () => { return (
-
+
Recipes diff --git a/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx b/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx index b6340485..865314cf 100644 --- a/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx +++ b/app/src/app/(libraries)/library/[slug]/explore/[[...path]]/page.tsx @@ -8,20 +8,15 @@ import React, { Suspense } from 'react' import { graphql, useLazyLoadQuery } from 'react-relay' import { useLibraryContext } from '@/app/(libraries)/library/[slug]/use-library.hook' -import { - ExploreBreadcrumbs, - ExploreContext, - ExploreControls, - ExploreTable, - useExplore, -} from '@/components/interfaces/explore' +import { ExploreContext, ExploreControls, ExploreTable, useExplore } from '@/components/interfaces/explore' +import { FileSystemBreadcrumb } from '@/components/interfaces/file-system' import { ResolutionWidget } from '@/components/widgets' const QUERY = graphql` query page_LibrarySlugExploreQuery($where: FileSystemDirectoryFilterInput, $order: [FileSystemEntrySortInput!]) { file_system_directory(where: $where) { id - ...ExploreBreadcrumbsFragment + ...FileSystemBreadcrumbFragment ...ExploreControlsFragment ...ExploreTableFragment @arguments(order: $order) } @@ -88,7 +83,7 @@ const LibraryExplorePage: React.FC = ({ params }) => { - + @@ -109,7 +104,7 @@ const LibraryExplorePage: React.FC = ({ params }) => {
-
+
Resolution diff --git a/app/src/components/interfaces/dashboard/index.ts b/app/src/components/interfaces/dashboard/index.ts deleted file mode 100644 index 86dccc03..00000000 --- a/app/src/components/interfaces/dashboard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as EncodeDialog } from '@/components/interfaces/dashboard/encode-dialog/EncodeDialog' diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx b/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx similarity index 89% rename from app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx rename to app/src/components/interfaces/encode/dialog/EncodeDialog.tsx index 113a59ea..fdcdbbc4 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx @@ -6,14 +6,14 @@ import { IconX } from '@tabler/icons-react' import React from 'react' import { graphql, useFragment, useSubscription } from 'react-relay' -import EncodeDialogSidebar from '@/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar' -import EncodeAnalyticsPanel from '@/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel' -import EncodeDialogScript from '@/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel' +import EncodeDialogSidebar from '@/components/interfaces/encode/dialog/EncodeDialogSidebar' +import EncodeAnalyticsPanel from '@/components/interfaces/encode/dialog/panels/EncodeAnalyticsPanel' +import EncodeDialogScript from '@/components/interfaces/encode/dialog/panels/EncodeScriptPanel' import { EncodeDialogContext, EncodeDialogPanel, useEncodeDialog, -} from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' +} from '@/components/interfaces/encode/dialog/use-encode-dialog.hook' import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' const FRAGMENT = graphql` diff --git a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx b/app/src/components/interfaces/encode/dialog/EncodeDialogSidebar.tsx similarity index 87% rename from app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx rename to app/src/components/interfaces/encode/dialog/EncodeDialogSidebar.tsx index 73a63381..5847f142 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/EncodeDialogSidebar.tsx +++ b/app/src/components/interfaces/encode/dialog/EncodeDialogSidebar.tsx @@ -1,10 +1,7 @@ import { Navigation } from '@giantnodes/react' import { IconReportAnalytics, IconScript } from '@tabler/icons-react' -import { - EncodeDialogPanel, - useEncodeDialogContext, -} from '@/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook' +import { EncodeDialogPanel, useEncodeDialogContext } from '@/components/interfaces/encode/dialog/use-encode-dialog.hook' const EncodeDialogSidebar = () => { const { panel, setPanel } = useEncodeDialogContext() diff --git a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx b/app/src/components/interfaces/encode/dialog/panels/EncodeAnalyticsPanel.tsx similarity index 71% rename from app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx rename to app/src/components/interfaces/encode/dialog/panels/EncodeAnalyticsPanel.tsx index 7365d0fb..2f9372fc 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeAnalyticsPanel.tsx +++ b/app/src/components/interfaces/encode/dialog/panels/EncodeAnalyticsPanel.tsx @@ -1,9 +1,9 @@ import type { EncodeAnalyticsPanelFragment$key } from '@/__generated__/EncodeAnalyticsPanelFragment.graphql' -import { Card } from '@giantnodes/react' +import { Card, Typography } from '@giantnodes/react' import { graphql, useFragment } from 'react-relay' -import EncodeSizeWidget from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget' +import { EncodeSizeWidget } from '@/components/interfaces/encode' const FRAGMENT = graphql` fragment EncodeAnalyticsPanelFragment on Encode { @@ -20,9 +20,11 @@ const EncodeAnalyticsPanel: React.FC = ({ $key }) => return ( - Size + + Size + - + diff --git a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx similarity index 64% rename from app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx rename to app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx index 46d0f55c..e2185db4 100644 --- a/app/src/components/interfaces/dashboard/encode-dialog/panels/EncodeScriptPanel.tsx +++ b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx @@ -1,10 +1,9 @@ import type { EncodeScriptPanelFragment$key } from '@/__generated__/EncodeScriptPanelFragment.graphql' -import { Card } from '@giantnodes/react' +import { Card, Typography } from '@giantnodes/react' import { graphql, useFragment } from 'react-relay' -import EncodeDialogCommand from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget' -import EncodeDialogLog from '@/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget' +import { EncodeCommandWidget, EncodeOutputWidget } from '@/components/interfaces/encode' const FRAGMENT = graphql` fragment EncodeScriptPanelFragment on Encode { @@ -23,18 +22,22 @@ const EncodeScriptPanel: React.FC = ({ $key }) => { return ( <> - Command + + Command + - + - Output + + Output + - + diff --git a/app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts b/app/src/components/interfaces/encode/dialog/use-encode-dialog.hook.ts similarity index 100% rename from app/src/components/interfaces/dashboard/encode-dialog/use-encode-dialog.hook.ts rename to app/src/components/interfaces/encode/dialog/use-encode-dialog.hook.ts diff --git a/app/src/components/interfaces/encode/index.ts b/app/src/components/interfaces/encode/index.ts new file mode 100644 index 00000000..115e7f0e --- /dev/null +++ b/app/src/components/interfaces/encode/index.ts @@ -0,0 +1,5 @@ +export { default as EncodeDialog } from '@/components/interfaces/encode/dialog/EncodeDialog' + +export { default as EncodeCommandWidget } from '@/components/interfaces/encode/widgets/EncodeCommandWidget' +export { default as EncodeOutputWidget } from '@/components/interfaces/encode/widgets/EncodeOutputWidget' +export { default as EncodeSizeWidget } from '@/components/interfaces/encode/widgets/EncodeSizeWidget' diff --git a/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx similarity index 100% rename from app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeCommandWidget.tsx rename to app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx diff --git a/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx similarity index 100% rename from app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeOutputWidget.tsx rename to app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx diff --git a/app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeSizeWidget.tsx similarity index 100% rename from app/src/components/interfaces/dashboard/encode-dialog/widgets/EncodeSizeWidget.tsx rename to app/src/components/interfaces/encode/widgets/EncodeSizeWidget.tsx diff --git a/app/src/components/interfaces/explore/index.ts b/app/src/components/interfaces/explore/index.ts index b305721c..f2cbeb64 100644 --- a/app/src/components/interfaces/explore/index.ts +++ b/app/src/components/interfaces/explore/index.ts @@ -1,4 +1,3 @@ -export { default as ExploreBreadcrumbs } from '@/components/interfaces/explore/breadcrumbs/ExploreBreadcrumbs' export { default as ExploreControls } from '@/components/interfaces/explore/controls/ExploreControls' export { default as ExploreTable } from '@/components/interfaces/explore/table/ExploreTable' diff --git a/app/src/components/interfaces/explore/breadcrumbs/ExploreBreadcrumbs.tsx b/app/src/components/interfaces/file-system/FileSystemBreadcrumb.tsx similarity index 84% rename from app/src/components/interfaces/explore/breadcrumbs/ExploreBreadcrumbs.tsx rename to app/src/components/interfaces/file-system/FileSystemBreadcrumb.tsx index b217baf8..23997f26 100644 --- a/app/src/components/interfaces/explore/breadcrumbs/ExploreBreadcrumbs.tsx +++ b/app/src/components/interfaces/file-system/FileSystemBreadcrumb.tsx @@ -1,11 +1,11 @@ -import type { ExploreBreadcrumbsFragment$key } from '@/__generated__/ExploreBreadcrumbsFragment.graphql' +import type { FileSystemBreadcrumbFragment$key } from '@/__generated__/FileSystemBreadcrumbFragment.graphql' import { Breadcrumb, Link } from '@giantnodes/react' import React from 'react' import { graphql, useFragment } from 'react-relay' const FRAGMENT = graphql` - fragment ExploreBreadcrumbsFragment on FileSystemDirectory { + fragment FileSystemBreadcrumbFragment on FileSystemEntry { library { slug path_info { @@ -20,11 +20,11 @@ const FRAGMENT = graphql` } ` -type ExploreBreadcrumbsProps = { - $key: ExploreBreadcrumbsFragment$key +type FileSystemBreadcrumbProps = { + $key: FileSystemBreadcrumbFragment$key } -const ExploreBreadcrumbs: React.FC = ({ $key }) => { +const FileSystemBreadcrumb: React.FC = ({ $key }) => { const data = useFragment(FRAGMENT, $key) const directories = React.useMemo>(() => { @@ -73,4 +73,4 @@ const ExploreBreadcrumbs: React.FC = ({ $key }) => { ) } -export default ExploreBreadcrumbs +export default FileSystemBreadcrumb diff --git a/app/src/components/interfaces/file-system/index.ts b/app/src/components/interfaces/file-system/index.ts new file mode 100644 index 00000000..0ac04af8 --- /dev/null +++ b/app/src/components/interfaces/file-system/index.ts @@ -0,0 +1 @@ +export { default as FileSystemBreadcrumb } from '@/components/interfaces/file-system/FileSystemBreadcrumb' diff --git a/app/src/components/interfaces/index.ts b/app/src/components/interfaces/index.ts deleted file mode 100644 index 93b310ed..00000000 --- a/app/src/components/interfaces/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@/components/interfaces/explore' diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index 68cc8fc2..3b7d6fef 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -10,7 +10,6 @@ import { IconProgressX } from '@tabler/icons-react' import React from 'react' import { graphql, useMutation, usePaginationFragment, useSubscription } from 'react-relay' -import { EncodeDialog } from '@/components/interfaces/dashboard' import { EncodeBadges } from '@/components/ui' const FRAGMENT = graphql` @@ -33,7 +32,6 @@ const FRAGMENT = graphql` } } ...EncodeBadgesFragment - ...EncodeDialogFragment } } pageInfo { @@ -132,11 +130,7 @@ const EncodingTable: React.FC = ({ $key }) => { {(item) => ( - - - + {item.node.file.path_info.name}
From 1b213b0a13874577d8ac25dbd3598ddc8552391f Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 18:48:04 +1000 Subject: [PATCH 14/19] fix: drop duplicate FileSystemEntryFilterType --- .../Types/Encodes/Filters/EncodeFilterType.cs | 14 ++++++++++++++ .../Encodes/Filters/FileSystemEntryFilterType.cs | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) create mode 100644 src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/EncodeFilterType.cs delete mode 100644 src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs diff --git a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/EncodeFilterType.cs b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/EncodeFilterType.cs new file mode 100644 index 00000000..aba98bbb --- /dev/null +++ b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/EncodeFilterType.cs @@ -0,0 +1,14 @@ +using Giantnodes.Service.Dashboard.Domain.Aggregates.Encodes; +using HotChocolate.Data.Filters; + +namespace Giantnodes.Service.Dashboard.HttpApi.Types.Encodes.Filters; + +public class EncodeFilterType : FilterInputType +{ + protected override void Configure(IFilterInputTypeDescriptor descriptor) + { + descriptor + .Field(p => p.Id) + .Type(); + } +} \ No newline at end of file diff --git a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs b/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs deleted file mode 100644 index a55db402..00000000 --- a/src/Service.Dashboard/src/HttpApi/Types/Encodes/Filters/FileSystemEntryFilterType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Giantnodes.Service.Dashboard.Domain.Aggregates.Entries; -using HotChocolate.Data.Filters; - -namespace Giantnodes.Service.Dashboard.HttpApi.Types.Entries.Filters; - -public class FileSystemEntryFilterType : FilterInputType -{ - protected override void Configure(IFilterInputTypeDescriptor descriptor) - { - descriptor - .Field(p => p.Id) - .Type(); - } -} \ No newline at end of file From ac1b96875de6a9f3406e48d2d508369e1492ef3b Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 18:50:25 +1000 Subject: [PATCH 15/19] fix: rename EncodeFindOne --- .../src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs index de40e843..ea8afadc 100644 --- a/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs +++ b/src/Service.Dashboard/src/HttpApi/Resolvers/Encodes/Queries/EncodeFindOne.cs @@ -5,13 +5,13 @@ namespace Giantnodes.Service.Dashboard.HttpApi.Resolvers.Encodes.Queries; [ExtendObjectType(OperationTypeNames.Query)] -public class EncodeFindMany +public class EncodeFindOne { - [UsePaging] + [UseFirstOrDefault] [UseProjection] [UseFiltering] [UseSorting] - public IQueryable Encodes([Service] ApplicationDbContext database) + public IQueryable Encode([Service] ApplicationDbContext database) { return database.Encodes.AsNoTracking(); } From 3380b3abdb88d9e2e219ca9f5b6ca7de8d108e76 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 19:48:31 +1000 Subject: [PATCH 16/19] feat: add encode operation details widget --- app/src/app/(dashboard)/encode/[id]/page.tsx | 28 +++++- .../dialog/panels/EncodeScriptPanel.tsx | 7 +- app/src/components/interfaces/encode/index.ts | 1 + .../encode/widgets/EncodeOperationWidget.tsx | 99 +++++++++++++++++++ .../ui/encode-badges/EncodeBadges.tsx | 7 +- app/src/utilities/numbers.ts | 5 + 6 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx create mode 100644 app/src/utilities/numbers.ts diff --git a/app/src/app/(dashboard)/encode/[id]/page.tsx b/app/src/app/(dashboard)/encode/[id]/page.tsx index f1e712db..0914a533 100644 --- a/app/src/app/(dashboard)/encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/encode/[id]/page.tsx @@ -2,19 +2,27 @@ import type { page_EncodedPage_Query } from '@/__generated__/page_EncodedPage_Query.graphql' -import { Card, Typography } from '@giantnodes/react' +import { Alert, Card, Typography } from '@giantnodes/react' +import { IconAlertCircleFilled } from '@tabler/icons-react' import { notFound } from 'next/navigation' import { graphql, useLazyLoadQuery } from 'react-relay' -import { EncodeCommandWidget, EncodeOutputWidget, EncodeSizeWidget } from '@/components/interfaces/encode' +import { + EncodeCommandWidget, + EncodeOperationWidget, + EncodeOutputWidget, + EncodeSizeWidget, +} from '@/components/interfaces/encode' import { FileSystemBreadcrumb } from '@/components/interfaces/file-system' const QUERY = graphql` query page_EncodedPage_Query($where: EncodeFilterInput) { encode(where: $where) { + failure_reason file { ...FileSystemBreadcrumbFragment } + ...EncodeOperationWidgetFragment ...EncodeCommandWidgetFragment ...EncodeOutputWidgetFragment ...EncodeSizeWidgetFragment @@ -51,6 +59,18 @@ const EncodePage: React.FC = ({ params }) => { + {query.encode.failure_reason && ( + + + + The encode operation encountered an error + + {query.encode.failure_reason} + + + + )} + Size @@ -84,9 +104,7 @@ const EncodePage: React.FC = ({ params }) => {
- - Tbd - +
diff --git a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx index e2185db4..1a482d9e 100644 --- a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx +++ b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx @@ -3,10 +3,11 @@ import type { EncodeScriptPanelFragment$key } from '@/__generated__/EncodeScript import { Card, Typography } from '@giantnodes/react' import { graphql, useFragment } from 'react-relay' -import { EncodeCommandWidget, EncodeOutputWidget } from '@/components/interfaces/encode' +import { EncodeCommandWidget, EncodeOperationWidget, EncodeOutputWidget } from '@/components/interfaces/encode' const FRAGMENT = graphql` fragment EncodeScriptPanelFragment on Encode { + ...EncodeOperationWidgetFragment ...EncodeCommandWidgetFragment ...EncodeOutputWidgetFragment } @@ -21,6 +22,10 @@ const EncodeScriptPanel: React.FC = ({ $key }) => { return ( <> + + + + Command diff --git a/app/src/components/interfaces/encode/index.ts b/app/src/components/interfaces/encode/index.ts index 115e7f0e..1f92f079 100644 --- a/app/src/components/interfaces/encode/index.ts +++ b/app/src/components/interfaces/encode/index.ts @@ -1,5 +1,6 @@ export { default as EncodeDialog } from '@/components/interfaces/encode/dialog/EncodeDialog' export { default as EncodeCommandWidget } from '@/components/interfaces/encode/widgets/EncodeCommandWidget' +export { default as EncodeOperationWidget } from '@/components/interfaces/encode/widgets/EncodeOperationWidget' export { default as EncodeOutputWidget } from '@/components/interfaces/encode/widgets/EncodeOutputWidget' export { default as EncodeSizeWidget } from '@/components/interfaces/encode/widgets/EncodeSizeWidget' diff --git a/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx new file mode 100644 index 00000000..dd80f3b8 --- /dev/null +++ b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx @@ -0,0 +1,99 @@ +import type { EncodeOperationWidgetFragment$key } from '@/__generated__/EncodeOperationWidgetFragment.graphql' + +import { Chip, Table, Typography } from '@giantnodes/react' +import dayjs from 'dayjs' +import { filesize } from 'filesize' +import { graphql, useFragment } from 'react-relay' + +import { percent } from '@/utilities/numbers' + +const FRAGMENT = graphql` + fragment EncodeOperationWidgetFragment on Encode { + percent + updated_at + machine { + name + user_name + } + speed { + bitrate + frames + scale + } + } +` + +type EncodeOperationWidgetProps = { + $key: EncodeOperationWidgetFragment$key +} + +const EncodeOperationWidget: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + return ( + + + + name + + + value + + + + {data.percent != null && ( + + + Progress + + + {percent(data.percent)} + + + )} + + + + Machine + + + {data.machine?.name} + {data.machine?.user_name} + + + + {data.speed != null && ( + + + Speed + + + {data.speed.frames} fps + + {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s + + {data.speed.scale.toFixed(2)}x + + + )} + + {data.updated_at != null && ( + + + Heartbeat + + + + + {dayjs(data.updated_at).fromNow()} + + + + + )} + +
+ ) +} + +export default EncodeOperationWidget diff --git a/app/src/components/ui/encode-badges/EncodeBadges.tsx b/app/src/components/ui/encode-badges/EncodeBadges.tsx index 94cb8340..b7aee9d6 100644 --- a/app/src/components/ui/encode-badges/EncodeBadges.tsx +++ b/app/src/components/ui/encode-badges/EncodeBadges.tsx @@ -9,6 +9,7 @@ import React from 'react' import { graphql, useFragment } from 'react-relay' import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' +import { percent } from '@/utilities/numbers' type EncodeBadgesProps = Omit & { $key: EncodeBadgesFragment$key @@ -40,12 +41,6 @@ const FRAGMENT = graphql` const EncodeBadges: React.FC = ({ $key, size }) => { const data = useFragment(FRAGMENT, $key) - const percent = (value: number): string => - Intl.NumberFormat('en-US', { - style: 'percent', - maximumFractionDigits: 2, - }).format(value) - const SizeChip = React.useCallback(() => { const difference = data.snapshots[data.snapshots.length - 1].size - data.snapshots[0].size const increase = Math.abs(difference / data.snapshots[0].size) diff --git a/app/src/utilities/numbers.ts b/app/src/utilities/numbers.ts new file mode 100644 index 00000000..10407dc6 --- /dev/null +++ b/app/src/utilities/numbers.ts @@ -0,0 +1,5 @@ +export const percent = (value: number): string => + Intl.NumberFormat('en-US', { + style: 'percent', + maximumFractionDigits: 2, + }).format(value) From 136e2bdae44fbfd01843a49242656df6339375dd Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sat, 11 May 2024 19:48:44 +1000 Subject: [PATCH 17/19] fix: nextjs unable to build --- .../app/(dashboard)/@dialog/(.)encode/[id]/page.tsx | 8 ++++---- app/src/app/(dashboard)/@dialog/default.tsx | 10 ++++++++-- app/src/app/(dashboard)/encode/[id]/page.tsx | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx index 509a3ce0..5f05383c 100644 --- a/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/@dialog/(.)encode/[id]/page.tsx @@ -15,13 +15,13 @@ const QUERY = graphql` } ` -type EncodePageProps = React.PropsWithChildren & { +type EncodePageProps = { params: { - id: string + [x: string]: never } } -const EncodePage: React.FC = ({ children, params }) => { +const EncodePage: React.FC = ({ params }) => { const router = useRouter() const query = useLazyLoadQuery(QUERY, { @@ -38,7 +38,7 @@ const EncodePage: React.FC = ({ children, params }) => { return ( - {children} + Open ) } diff --git a/app/src/app/(dashboard)/@dialog/default.tsx b/app/src/app/(dashboard)/@dialog/default.tsx index 064fd6d1..6561c832 100644 --- a/app/src/app/(dashboard)/@dialog/default.tsx +++ b/app/src/app/(dashboard)/@dialog/default.tsx @@ -1,3 +1,9 @@ -const Default = () => null +type DialogDefaultProps = { + params: { + id: string + } +} -export default Default +const DialogDefault: React.FC = () => null + +export default DialogDefault diff --git a/app/src/app/(dashboard)/encode/[id]/page.tsx b/app/src/app/(dashboard)/encode/[id]/page.tsx index 0914a533..9b4921ac 100644 --- a/app/src/app/(dashboard)/encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/encode/[id]/page.tsx @@ -30,9 +30,9 @@ const QUERY = graphql` } ` -type EncodePageProps = React.PropsWithChildren & { +type EncodePageProps = { params: { - id: string + [x: string]: never } } From 6506c2bf0465d9c0d5ba9ee30c72e718069b1335 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sun, 12 May 2024 11:11:39 +1000 Subject: [PATCH 18/19] refactor: use separate encode chip components --- app/src/app/(dashboard)/encode/[id]/page.tsx | 12 +- .../encode/chips/EncodeDuration.tsx | 47 ++++++ .../interfaces/encode/chips/EncodePercent.tsx | 51 +++++++ .../interfaces/encode/chips/EncodeSpeed.tsx | 62 ++++++++ .../interfaces/encode/chips/EncodeStatus.tsx | 76 ++++++++++ .../interfaces/encode/dialog/EncodeDialog.tsx | 19 +-- .../dialog/panels/EncodeScriptPanel.tsx | 16 +- app/src/components/interfaces/encode/index.ts | 5 + .../encode/widgets/EncodeCommandWidget.tsx | 2 +- .../encode/widgets/EncodeOperationWidget.tsx | 74 ++++----- .../tables/encoded/EncodedTable.tsx | 7 +- .../tables/encoding/EncodingTable.tsx | 37 +---- .../ui/encode-badges/EncodeBadges.tsx | 140 ------------------ .../ui/encode-badges/EncodeStatusBadge.tsx | 50 ------- app/src/components/ui/index.ts | 2 +- 15 files changed, 305 insertions(+), 295 deletions(-) create mode 100644 app/src/components/interfaces/encode/chips/EncodeDuration.tsx create mode 100644 app/src/components/interfaces/encode/chips/EncodePercent.tsx create mode 100644 app/src/components/interfaces/encode/chips/EncodeSpeed.tsx create mode 100644 app/src/components/interfaces/encode/chips/EncodeStatus.tsx delete mode 100644 app/src/components/ui/encode-badges/EncodeBadges.tsx delete mode 100644 app/src/components/ui/encode-badges/EncodeStatusBadge.tsx diff --git a/app/src/app/(dashboard)/encode/[id]/page.tsx b/app/src/app/(dashboard)/encode/[id]/page.tsx index 9b4921ac..46196102 100644 --- a/app/src/app/(dashboard)/encode/[id]/page.tsx +++ b/app/src/app/(dashboard)/encode/[id]/page.tsx @@ -53,12 +53,6 @@ const EncodePage: React.FC = ({ params }) => {
- - - - - - {query.encode.failure_reason && ( @@ -71,6 +65,12 @@ const EncodePage: React.FC = ({ params }) => { )} + + + + + + Size diff --git a/app/src/components/interfaces/encode/chips/EncodeDuration.tsx b/app/src/components/interfaces/encode/chips/EncodeDuration.tsx new file mode 100644 index 00000000..475a6c05 --- /dev/null +++ b/app/src/components/interfaces/encode/chips/EncodeDuration.tsx @@ -0,0 +1,47 @@ +import type { EncodeDurationFragment$key } from '@/__generated__/EncodeDurationFragment.graphql' + +import { Chip } from '@giantnodes/react' +import dayjs from 'dayjs' +import React from 'react' +import { graphql, useFragment } from 'react-relay' + +const FRAGMENT = graphql` + fragment EncodeDurationFragment on Encode { + status + failed_at + cancelled_at + completed_at + created_at + } +` + +type EncodeDurationChipProps = { + $key: EncodeDurationFragment$key +} + +const EncodeDuration: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + const date = React.useMemo(() => { + switch (data.status) { + case 'COMPLETED': + return data.completed_at + case 'CANCELLED': + return data.cancelled_at + + case 'FAILED': + return data.failed_at + + default: + return undefined + } + }, [data.cancelled_at, data.completed_at, data.failed_at, data.status]) + + return ( + + {dayjs.duration(dayjs(date).diff(data.created_at)).format('H[h] m[m] s[s]')} + + ) +} + +export default EncodeDuration diff --git a/app/src/components/interfaces/encode/chips/EncodePercent.tsx b/app/src/components/interfaces/encode/chips/EncodePercent.tsx new file mode 100644 index 00000000..ded839a2 --- /dev/null +++ b/app/src/components/interfaces/encode/chips/EncodePercent.tsx @@ -0,0 +1,51 @@ +import type { EncodePercentFragment$key } from '@/__generated__/EncodePercentFragment.graphql' + +import { Chip } from '@giantnodes/react' +import React from 'react' +import { graphql, useFragment, useSubscription } from 'react-relay' + +import { percent } from '@/utilities/numbers' + +const FRAGMENT = graphql` + fragment EncodePercentFragment on Encode { + id + percent + } +` + +const SUBSCRIPTION = graphql` + subscription EncodePercentSubscription($where: EncodeFilterInput) { + encode_progressed(where: $where) { + ...EncodePercentFragment + } + } +` + +type EncodePercentProps = { + $key: EncodePercentFragment$key +} + +const EncodePercent: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + useSubscription({ + subscription: SUBSCRIPTION, + variables: { + variables: { + where: { + id: { + eq: data.id, + }, + }, + }, + }, + }) + + if (data.percent == null) { + return undefined + } + + return {percent(data.percent)} +} + +export default EncodePercent diff --git a/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx b/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx new file mode 100644 index 00000000..5b1d6d47 --- /dev/null +++ b/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx @@ -0,0 +1,62 @@ +import type { EncodeSpeedFragment$key } from '@/__generated__/EncodeSpeedFragment.graphql' + +import { Chip } from '@giantnodes/react' +import { filesize } from 'filesize' +import React from 'react' +import { graphql, useFragment, useSubscription } from 'react-relay' + +const FRAGMENT = graphql` + fragment EncodeSpeedFragment on Encode { + id + speed { + frames + bitrate + scale + } + } +` + +const SUBSCRIPTION = graphql` + subscription EncodeSpeedSubscription($where: EncodeFilterInput) { + encode_speed_change(where: $where) { + ...EncodeSpeedFragment + } + } +` + +type EncodeSpeedProps = { + $key: EncodeSpeedFragment$key +} + +const EncodeSpeed: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + useSubscription({ + subscription: SUBSCRIPTION, + variables: { + variables: { + where: { + id: { + eq: data.id, + }, + }, + }, + }, + }) + + if (data.speed == null) { + return undefined + } + + return ( + <> + {data.speed.frames} fps + + {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s + + {data.speed.scale.toFixed(2)}x + + ) +} + +export default EncodeSpeed diff --git a/app/src/components/interfaces/encode/chips/EncodeStatus.tsx b/app/src/components/interfaces/encode/chips/EncodeStatus.tsx new file mode 100644 index 00000000..c4da260f --- /dev/null +++ b/app/src/components/interfaces/encode/chips/EncodeStatus.tsx @@ -0,0 +1,76 @@ +import type { EncodeStatusFragment$key } from '@/__generated__/EncodeStatusFragment.graphql' +import type { ChipProps } from '@giantnodes/react' + +import { Chip } from '@giantnodes/react' +import dayjs from 'dayjs' +import React from 'react' +import { graphql, useFragment } from 'react-relay' + +const FRAGMENT = graphql` + fragment EncodeStatusFragment on Encode { + status + failed_at + cancelled_at + completed_at + } +` + +type EncodeStatusChipProps = { + $key: EncodeStatusFragment$key +} + +const EncodeStatus: React.FC = ({ $key }) => { + const data = useFragment(FRAGMENT, $key) + + const color = React.useMemo(() => { + switch (data.status) { + case 'SUBMITTED': + return 'info' + + case 'QUEUED': + return 'info' + + case 'ENCODING': + return 'success' + + case 'DEGRADED': + return 'warning' + + case 'COMPLETED': + return 'success' + + case 'CANCELLED': + return 'neutral' + + case 'FAILED': + return 'danger' + + default: + return 'neutral' + } + }, [data.status]) + + const title = React.useMemo(() => { + switch (data.status) { + case 'COMPLETED': + return dayjs(data.completed_at).format('L LT') + + case 'CANCELLED': + return dayjs(data.cancelled_at).format('L LT') + + case 'FAILED': + return dayjs(data.failed_at).format('L LT') + + default: + return undefined + } + }, [data.cancelled_at, data.completed_at, data.failed_at, data.status]) + + return ( + + {data.status.toLowerCase()} + + ) +} + +export default EncodeStatus diff --git a/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx b/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx index fdcdbbc4..f12cb378 100644 --- a/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx +++ b/app/src/components/interfaces/encode/dialog/EncodeDialog.tsx @@ -4,7 +4,7 @@ import type { DialogProps } from '@giantnodes/react' import { Button, Card, Chip, Dialog, Typography } from '@giantnodes/react' import { IconX } from '@tabler/icons-react' import React from 'react' -import { graphql, useFragment, useSubscription } from 'react-relay' +import { graphql, useFragment } from 'react-relay' import EncodeDialogSidebar from '@/components/interfaces/encode/dialog/EncodeDialogSidebar' import EncodeAnalyticsPanel from '@/components/interfaces/encode/dialog/panels/EncodeAnalyticsPanel' @@ -14,7 +14,6 @@ import { EncodeDialogPanel, useEncodeDialog, } from '@/components/interfaces/encode/dialog/use-encode-dialog.hook' -import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' const FRAGMENT = graphql` fragment EncodeDialogFragment on Encode { @@ -26,20 +25,11 @@ const FRAGMENT = graphql` name } } - ...EncodeStatusBadgeFragment ...EncodeScriptPanelFragment ...EncodeAnalyticsPanelFragment } ` -const OUTPUTTED_SUBSCRIPTION = graphql` - subscription EncodeDialogOutputtedSubscription { - encode_outputted { - output - } - } -` - type EncodeDialogProps = React.PropsWithChildren & { $key: EncodeDialogFragment$key } & DialogProps @@ -48,11 +38,6 @@ const EncodeDialog: React.FC = ({ $key, children, ...rest }) const data = useFragment(FRAGMENT, $key) const context = useEncodeDialog({ panel: EncodeDialogPanel.SCRIPT }) - useSubscription({ - subscription: OUTPUTTED_SUBSCRIPTION, - variables: {}, - }) - const content = React.useCallback(() => { switch (context.panel) { case EncodeDialogPanel.SCRIPT: @@ -82,8 +67,6 @@ const EncodeDialog: React.FC = ({ $key, children, ...rest })
{data.file.path_info.name} - - {data.recipe.name}
diff --git a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx index 1a482d9e..8343a5eb 100644 --- a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx +++ b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx @@ -1,12 +1,14 @@ import type { EncodeScriptPanelFragment$key } from '@/__generated__/EncodeScriptPanelFragment.graphql' -import { Card, Typography } from '@giantnodes/react' +import { Alert, Card, Typography } from '@giantnodes/react' +import { IconAlertCircleFilled } from '@tabler/icons-react' import { graphql, useFragment } from 'react-relay' import { EncodeCommandWidget, EncodeOperationWidget, EncodeOutputWidget } from '@/components/interfaces/encode' const FRAGMENT = graphql` fragment EncodeScriptPanelFragment on Encode { + failure_reason ...EncodeOperationWidgetFragment ...EncodeCommandWidgetFragment ...EncodeOutputWidgetFragment @@ -22,6 +24,18 @@ const EncodeScriptPanel: React.FC = ({ $key }) => { return ( <> + {data.failure_reason && ( + + + + The encode operation encountered an error + + {data.failure_reason} + + + + )} + diff --git a/app/src/components/interfaces/encode/index.ts b/app/src/components/interfaces/encode/index.ts index 1f92f079..7680869b 100644 --- a/app/src/components/interfaces/encode/index.ts +++ b/app/src/components/interfaces/encode/index.ts @@ -1,3 +1,8 @@ +export { default as EncodeDuration } from '@/components/interfaces/encode/chips/EncodeDuration' +export { default as EncodePercent } from '@/components/interfaces/encode/chips/EncodePercent' +export { default as EncodeSpeed } from '@/components/interfaces/encode/chips/EncodeSpeed' +export { default as EncodeStatus } from '@/components/interfaces/encode/chips/EncodeStatus' + export { default as EncodeDialog } from '@/components/interfaces/encode/dialog/EncodeDialog' export { default as EncodeCommandWidget } from '@/components/interfaces/encode/widgets/EncodeCommandWidget' diff --git a/app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx index f9e78213..887f9853 100644 --- a/app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx +++ b/app/src/components/interfaces/encode/widgets/EncodeCommandWidget.tsx @@ -2,7 +2,7 @@ import type { EncodeCommandWidgetFragment$key } from '@/__generated__/EncodeComm import { graphql, useFragment } from 'react-relay' -import CodeBlock from '@/components/ui/code-block/CodeBlock' +import { CodeBlock } from '@/components/ui' const FRAGMENT = graphql` fragment EncodeCommandWidgetFragment on Encode { diff --git a/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx index dd80f3b8..05166a84 100644 --- a/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx +++ b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx @@ -2,24 +2,21 @@ import type { EncodeOperationWidgetFragment$key } from '@/__generated__/EncodeOp import { Chip, Table, Typography } from '@giantnodes/react' import dayjs from 'dayjs' -import { filesize } from 'filesize' import { graphql, useFragment } from 'react-relay' -import { percent } from '@/utilities/numbers' +import { EncodePercent, EncodeSpeed, EncodeStatus } from '@/components/interfaces/encode' const FRAGMENT = graphql` fragment EncodeOperationWidgetFragment on Encode { - percent + id updated_at machine { name user_name } - speed { - bitrate - frames - scale - } + ...EncodePercentFragment + ...EncodeStatusFragment + ...EncodeSpeedFragment } ` @@ -31,51 +28,56 @@ const EncodeOperationWidget: React.FC = ({ $key }) = const data = useFragment(FRAGMENT, $key) return ( - +
name - value + value - {data.percent != null && ( - - - Progress - - - {percent(data.percent)} - - - )} + + + Status + + + + + + + + + Progress + + + + + Machine - {data.machine?.name} - {data.machine?.user_name} + {data.machine != null && ( + <> + {data.machine?.name} + {data.machine?.user_name} + + )} - {data.speed != null && ( - - - Speed - - - {data.speed.frames} fps - - {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s - - {data.speed.scale.toFixed(2)}x - - - )} + + + Speed + + + + + {data.updated_at != null && ( diff --git a/app/src/components/tables/encoded/EncodedTable.tsx b/app/src/components/tables/encoded/EncodedTable.tsx index 78a24c4e..7dadbc31 100644 --- a/app/src/components/tables/encoded/EncodedTable.tsx +++ b/app/src/components/tables/encoded/EncodedTable.tsx @@ -5,8 +5,6 @@ import { Button, Link, Table } from '@giantnodes/react' import React from 'react' import { graphql, usePaginationFragment } from 'react-relay' -import { EncodeBadges } from '@/components/ui' - const FRAGMENT = graphql` fragment EncodedTableFragment on Query @refetchable(queryName: "EncodedTableRefetchQuery") @@ -26,7 +24,6 @@ const FRAGMENT = graphql` name } } - ...EncodeBadgesFragment } } pageInfo { @@ -62,9 +59,7 @@ const EncodedTable: React.FC = ({ $key }) => { {item.node.file.path_info.name} - - - + Tbd )} diff --git a/app/src/components/tables/encoding/EncodingTable.tsx b/app/src/components/tables/encoding/EncodingTable.tsx index 3b7d6fef..855cdab7 100644 --- a/app/src/components/tables/encoding/EncodingTable.tsx +++ b/app/src/components/tables/encoding/EncodingTable.tsx @@ -8,9 +8,7 @@ import type { EncodingTableRefetchQuery } from '@/__generated__/EncodingTableRef import { Button, Link, Table } from '@giantnodes/react' import { IconProgressX } from '@tabler/icons-react' import React from 'react' -import { graphql, useMutation, usePaginationFragment, useSubscription } from 'react-relay' - -import { EncodeBadges } from '@/components/ui' +import { graphql, useMutation, usePaginationFragment } from 'react-relay' const FRAGMENT = graphql` fragment EncodingTableFragment on Query @@ -31,7 +29,6 @@ const FRAGMENT = graphql` name } } - ...EncodeBadgesFragment } } pageInfo { @@ -59,26 +56,6 @@ const MUTATION = graphql` } ` -const PROGRESSED_SUBSCRIPTION = graphql` - subscription EncodingTableProgressedSubscription { - encode_progressed { - percent - } - } -` - -const SPEED_SUBSCRIPTION = graphql` - subscription EncodingTableSpeedSubscription { - encode_speed_change { - speed { - frames - bitrate - scale - } - } - } -` - type EncodeEntry = NonNullable['edges']>[0]['node'] type EncodingTableProps = { @@ -93,16 +70,6 @@ const EncodingTable: React.FC = ({ $key }) => { const [commit] = useMutation(MUTATION) - useSubscription({ - subscription: PROGRESSED_SUBSCRIPTION, - variables: {}, - }) - - useSubscription({ - subscription: SPEED_SUBSCRIPTION, - variables: {}, - }) - const cancel = React.useCallback( (entry: EncodeEntry) => { commit({ @@ -134,8 +101,6 @@ const EncodingTable: React.FC = ({ $key }) => {
- - diff --git a/app/src/components/ui/encode-badges/EncodeBadges.tsx b/app/src/components/ui/encode-badges/EncodeBadges.tsx deleted file mode 100644 index b7aee9d6..00000000 --- a/app/src/components/ui/encode-badges/EncodeBadges.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import type { EncodeBadgesFragment$key } from '@/__generated__/EncodeBadgesFragment.graphql' -import type { ChipProps } from '@giantnodes/react' - -import { Chip } from '@giantnodes/react' -import { IconArrowRight, IconTrendingDown, IconTrendingUp } from '@tabler/icons-react' -import dayjs from 'dayjs' -import { filesize } from 'filesize' -import React from 'react' -import { graphql, useFragment } from 'react-relay' - -import EncodeStatusBadge from '@/components/ui/encode-badges/EncodeStatusBadge' -import { percent } from '@/utilities/numbers' - -type EncodeBadgesProps = Omit & { - $key: EncodeBadgesFragment$key -} - -const FRAGMENT = graphql` - fragment EncodeBadgesFragment on Encode { - status - percent - started_at - failure_reason - failed_at - cancelled_at - completed_at - created_at - speed { - frames - bitrate - scale - } - snapshots { - size - probed_at - } - ...EncodeStatusBadgeFragment - } -` - -const EncodeBadges: React.FC = ({ $key, size }) => { - const data = useFragment(FRAGMENT, $key) - - const SizeChip = React.useCallback(() => { - const difference = data.snapshots[data.snapshots.length - 1].size - data.snapshots[0].size - const increase = Math.abs(difference / data.snapshots[0].size) - - const icon = () => { - switch (true) { - case increase > 0: - return - - case increase < 0: - return - - case increase === 0: - default: - return - } - } - - const color = () => { - switch (true) { - case increase > 0: - return 'danger' - - case increase < 0: - return 'success' - - case increase === 0: - default: - return 'info' - } - } - - return ( - - {icon()} - - {percent(increase)} - - ) - }, [data.snapshots, size]) - - return ( -
- - - {data.status !== 'COMPLETED' && data.status !== 'CANCELLED' && data.status !== 'FAILED' && ( - <> - {data.speed != null && ( - <> - - {data.speed.frames} fps - - - - {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s - - - - {data.speed.scale.toFixed(2)}x - - - )} - - {data.percent != null && ( - - {percent(data.percent)} - - )} - - )} - - {data.status === 'COMPLETED' && ( - <> - - {dayjs.duration(dayjs(data.completed_at).diff(data.created_at)).format('H[h] m[m] s[s]')} - - - {SizeChip()} - - )} - - {data.status === 'CANCELLED' && ( - - {dayjs.duration(dayjs(data.cancelled_at).diff(data.created_at)).format('H[h] m[m] s[s]')} - - )} - - {data.status === 'FAILED' && ( - - {dayjs.duration(dayjs(data.failed_at).diff(data.created_at)).format('H[h] m[m] s[s]')} - - )} -
- ) -} - -export default EncodeBadges diff --git a/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx b/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx deleted file mode 100644 index f5c2636d..00000000 --- a/app/src/components/ui/encode-badges/EncodeStatusBadge.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { EncodeStatus, EncodeStatusBadgeFragment$key } from '@/__generated__/EncodeStatusBadgeFragment.graphql' - -import { Chip } from '@giantnodes/react' -import { graphql, useFragment } from 'react-relay' - -type EncodeStatusBadgeProps = { - $key: EncodeStatusBadgeFragment$key -} - -const FRAGMENT = graphql` - fragment EncodeStatusBadgeFragment on Encode { - status - } -` - -const EncodeStatusBadge: React.FC = ({ $key }) => { - const data = useFragment(FRAGMENT, $key) - - const color = (status: EncodeStatus) => { - switch (status) { - case 'SUBMITTED': - return 'info' - - case 'QUEUED': - return 'info' - - case 'ENCODING': - return 'success' - - case 'DEGRADED': - return 'warning' - - case 'COMPLETED': - return 'success' - - case 'CANCELLED': - return 'neutral' - - case 'FAILED': - return 'danger' - - default: - return 'neutral' - } - } - - return {data.status.toLowerCase()} -} - -export default EncodeStatusBadge diff --git a/app/src/components/ui/index.ts b/app/src/components/ui/index.ts index 8626f0ed..cb91940b 100644 --- a/app/src/components/ui/index.ts +++ b/app/src/components/ui/index.ts @@ -1 +1 @@ -export { default as EncodeBadges } from '@/components/ui/encode-badges/EncodeBadges' +export { default as CodeBlock } from '@/components/ui/code-block/CodeBlock' From f9b80842f2d81c9b9ee82d5f379754f2f474c22f Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Sun, 12 May 2024 11:24:32 +1000 Subject: [PATCH 19/19] feat: add flag to anchor encode output + chip color changes --- .../interfaces/encode/chips/EncodeSpeed.tsx | 4 ++-- .../encode/dialog/panels/EncodeScriptPanel.tsx | 2 +- .../encode/widgets/EncodeOperationWidget.tsx | 5 ++++- .../encode/widgets/EncodeOutputWidget.tsx | 16 ++++++++++------ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx b/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx index 5b1d6d47..b3482efb 100644 --- a/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx +++ b/app/src/components/interfaces/encode/chips/EncodeSpeed.tsx @@ -50,9 +50,9 @@ const EncodeSpeed: React.FC = ({ $key }) => { return ( <> - {data.speed.frames} fps + {data.speed.frames} fps - {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s + {filesize(data.speed.bitrate * 0.125, { bits: true }).toLowerCase()}/s {data.speed.scale.toFixed(2)}x diff --git a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx index 8343a5eb..7cabbd26 100644 --- a/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx +++ b/app/src/components/interfaces/encode/dialog/panels/EncodeScriptPanel.tsx @@ -56,7 +56,7 @@ const EncodeScriptPanel: React.FC = ({ $key }) => { - + diff --git a/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx index 05166a84..69fe1e91 100644 --- a/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx +++ b/app/src/components/interfaces/encode/widgets/EncodeOperationWidget.tsx @@ -86,7 +86,10 @@ const EncodeOperationWidget: React.FC = ({ $key }) = - + {dayjs(data.updated_at).fromNow()} diff --git a/app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx b/app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx index 051e2b8f..e8a12a41 100644 --- a/app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx +++ b/app/src/components/interfaces/encode/widgets/EncodeOutputWidget.tsx @@ -1,6 +1,7 @@ import type { EncodeOutputWidgetFragment$key } from '@/__generated__/EncodeOutputWidgetFragment.graphql' import type { EncodeOutputWidgetSubscription } from '@/__generated__/EncodeOutputWidgetSubscription.graphql' +import React from 'react' import { graphql, useFragment, useSubscription } from 'react-relay' import CodeBlock from '@/components/ui/code-block/CodeBlock' @@ -23,9 +24,10 @@ const SUBSCRIPTION = graphql` type EncodeOutputWidgetProps = { $key: EncodeOutputWidgetFragment$key + isAnchored?: boolean } -const EncodeOutputWidget: React.FC = ({ $key }) => { +const EncodeOutputWidget: React.FC = ({ $key, isAnchored }) => { const data = useFragment(FRAGMENT, $key) useSubscription({ @@ -39,11 +41,13 @@ const EncodeOutputWidget: React.FC = ({ $key }) => { }, }) - return ( - - {data.output} - - ) + const block = React.useCallback(() => {data.output}, [data.output]) + + return isAnchored ? {block()} : block() +} + +EncodeOutputWidget.defaultProps = { + isAnchored: false, } export default EncodeOutputWidget