Telerik Forums
UI for Blazor Forum
0 answers
174 views

Hello,

I am trying to implement a progress bar while importing an excel file. The progress bar does not work, I implement the sample within my code and its not working anyway.

Here is the code :

 

@page "/administration/excel"

@inject HttpClient httpClient
@inject IJSRuntime jSRuntime

@implements IDisposable

@using System.Timers

@using XxxPathie.Libraries.Shared.Models.Tools;
@using XxxPathie.Libraries.Ui.Tools.Network;

<h3>Tools</h3>

<label for="specialites">Spécialités</label>
<InputFile id="specialites" OnChange="OnSpecialitesChange">Specialités</InputFile>
<br />
<label for="motscles">Mots clés</label>
<InputFile id="motscles" OnChange="OnMotsClesChange">Mots clés</InputFile>

<br />
<label for="principesactifs">Principes actifs</label>
<InputFile id="principesactifs" OnChange="OnPrincipesActifsChange">Principes actifs</InputFile>

<br />
<label for="preparations">Préparations</label>
<InputFile id="preparations" OnChange="OnPreparationsChange">Préparations</InputFile>

<br />
<br />

<TelerikProgressBar @ref="progressBar" Value="@progressValue" Max="progressMax"></TelerikProgressBar>

<TelerikProgressBar Value="@ProgressValue" Max="100"></TelerikProgressBar>

@code {
    TelerikProgressBar progressBar;
    Timer timer = new();
    ExcelFile progress = new();
    double progressValue = 0;
    double progressMax = 1000;
    bool refresh = false;

    public void Dispose()
    {
        StopProgress();
        Timer?.Close();
    }

    protected override void OnAfterRender(bool firstRender)
    {
        if (Timer.Enabled == false)
        {

            Timer.Start();
        }
    }

    public void OnTimerElapsed(Object source, ElapsedEventArgs e)
    {
        if (ProgressValue < 100)
        {
            UpdateProgress();
        }
        else
        {
            StopProgress();
        }
    }

    public void UpdateProgress()
    {
        ProgressValue += ProgressStep;

        InvokeAsync(StateHasChanged);
    }

    public void StopProgress()
    {
        Timer?.Stop();
    }

    public const int TimerInterval = 1000;
    public const int TotalTime = 10 * TimerInterval;
    public double ProgressValue = 0;
    public int ProgressStep = 100 / (TotalTime / TimerInterval);
    public Timer Timer { get; set; } = new Timer();

    private async void OnSpecialitesChange(InputFileChangeEventArgs args)
    {
        await ManageDownload(args.File, "excel/specialites", "Spécialités.xlsx");
    }

    private async void OnMotsClesChange(InputFileChangeEventArgs args)
    {
        await ManageDownload(args.File, "excel/motscles", "Mots clés.xlsx");
    }

    private async void OnPrincipesActifsChange(InputFileChangeEventArgs args)
    {
        await ManageDownload(args.File, "excel/principesactifs", "Principes actifs.xlsx");
    }

    private async void OnPreparationsChange(InputFileChangeEventArgs args)
    {
        await ManageDownload(args.File, "excel/preparations", "Préparations.xlsx");
    }

    private async Task ManageDownload(IBrowserFile file, string url, string fileName)
    {
        byte[] bytes = new byte[file.Size];

        using (var stream = file.OpenReadStream(250 * 1024 * 1024))
        {
            await stream.ReadAsync(bytes, 0, bytes.Length);
        }

        progress = await RestClient.Instance.PostAsync<ExcelFile>(httpClient, url, bytes);

        progressValue = 0;
        progressMax = progress.MaxCount;

        refresh = !refresh;

        timer.Stop();
        timer.Interval = 200;
        timer.AutoReset = true;
        timer.Elapsed -= ProgressPlease;
        timer.Elapsed += ProgressPlease;
        timer.Start();
    }

    private void ProgressPlease(Object source, ElapsedEventArgs e)
    {
        progressValue++;
        progressMax = 1000;
    }

    private async Task ProgressReport(ExcelFile progress, string fileName)
    {
        try
        {
            for (; ; )
            {
                progress = await PoolProgress(progress, "fileName");
                progressMax = progress.MaxCount;
                progressValue = progress.DoneCount;
            }
        }
        catch (Exception exception)
        {
            var breakime = 1;
        }
    }

    private async Task<ExcelFile> PoolProgress(ExcelFile progress, string fileName)
    {
        progress = await RestClient.Instance.GetAsync<ExcelFile>(httpClient, "excel/progress/" + progress.Identifier);

        if (progress.DoneCount == progress.MaxCount && progress.bytes != null)
        {
            var href = httpClient.BaseAddress + progress.Identifier.ToString();
            await jSRuntime.InvokeAsync<object>("triggerFileDownload", fileName, href);

            throw new Exception("OK");
        }
        else if (progress.Identifier == Guid.Empty)
        {
            return progress;
        }
        else if (progress.DoneCount == progress.MaxCount)
        {
            throw new Exception("OK");
        }

        return progress;
    }
}

John
Top achievements
Rank 1
 asked on 15 Dec 2022
0 answers
296 views

Hi,

I am working on a dynamic form generator which is capable of generating a form based on a Json formdefinition retrieved from the server. I had some hurdles, but so far things are progressing nicely. Here's a summary of how it works (or should work):

  • FormDefinition with FormFields is retrieved from the server
  • FormModel is created dynamically using PropertyBuilder and AttributeBuilder
  • Form uses an EditContext which takes the generated FormModel as an input

TelerikTextBox and TelerikRadioButtonGroup work like a charm (they both use string properties of the dynamically created model). However the TelerikNumericTextBox and TelerikDateTimePicker generate the following error (and show a red line in the razor file:

Error (active)	CS0411	The type arguments for method 'TypeInference.CreateTelerikNumericTextBox_0<T>(RenderTreeBuilder, int, int, string, int, T, int, Expression<Func<T>>, int, int, int, T, int, string, int, EventCallback<object>, int, EventCallback<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Error (active)	CS0411	The type arguments for method 'TypeInference.CreateTelerikDatePicker_1<T>(RenderTreeBuilder, int, int, string, int, T, int, Expression<Func<T>>, int, EventCallback<object>, int, EventCallback<T>, int, string)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

I assume it has something to do with the type of the propery, but I am not sure. Here's the full code of the dynamic form so far.

@namespace TrueLime.Blazor.TrueForms
@inherits TrueFormBase

<TelerikForm EditContext="@FormEditContext"
             OnSubmit="@HandleSubmit">
    <FormValidation>
        <DataAnnotationsValidator></DataAnnotationsValidator>
    </FormValidation>
    <FormItems>

        @foreach (var propertyInfo in GetFormFieldProperties())
        {
            <FormItem Field="@(propertyInfo.Name)">
                <Template>
                    <div class="mb-1 row">
                        <label for="@(propertyInfo.Name)" class="k-label k-form-label col-sm-2">@propertyInfo.DisplayName()</label>
                        <div class="col-sm-8">

                            @switch (GetFormControlType(propertyInfo.Name))
                            {
                                case "textbox":
                                    <TelerikTextBox Id="@(propertyInfo.Name)"
                                                    Value="@GetStringValue(propertyInfo.Name)"
                                                    ValueExpression="@(() => propertyInfo.Name)"
                                                    OnChange="OnChange"
                                                    ValueChanged="@(value => OnValueChanged(value, propertyInfo.Name))"
                                                    InputMode="text"
                                                    PlaceHolder="@propertyInfo.PlaceholderDescription()">
                                    </TelerikTextBox>
                                    break;
                                case "decimaltextbox":
                                    <TelerikNumericTextBox Id="@(propertyInfo.Name)"
                                                           Value="@GetDecimalValue(propertyInfo.Name)"
                                                           ValueExpression="@(() => propertyInfo.Name)"
                                                           Decimals="2"
                                                           Step="0.01m"
                                                           Format="@CurrencyFormat"
                                                           OnChange="OnChange"
                                                           ValueChanged="@(value => OnValueChanged(value, propertyInfo.Name))">
                                    </TelerikNumericTextBox>
                                    break;
                                case "datepicker":
                                    <TelerikDatePicker Id="@(propertyInfo.Name)"
                                                    Value="@GetDateTimeValue(propertyInfo.Name)"
                                                    ValueExpression="@(() => propertyInfo.Name)"
                                                    OnChange="OnChange"
                                                    ValueChanged="@((DateTime value) => OnValueChanged(value, propertyInfo.Name))"
                                                    Placeholder="@propertyInfo.PlaceholderDescription()"
                                                    >
                                    </TelerikDatePicker>
                                    break;
                                case "radiogroup":
                                    <TelerikRadioGroup Id="@(propertyInfo.Name)"
                                                       Value="@GetStringValue(propertyInfo.Name)"
                                                       ValueExpression="@(() => propertyInfo.Name)"
                                                       OnChange="OnChange"
                                                       ValueChanged="@((string value) => OnValueChanged(value, propertyInfo.Name))"
                                                       Layout="@GetRadioGroupLayout(propertyInfo.Name)"
                                                       Data="@GetFormOptions(propertyInfo.Name)"
                                                       ValueField="@nameof(FormOption.Value)"
                                                       TextField="@nameof(FormOption.Text)">
                                    </TelerikRadioGroup>
                                    break;
                            }
                        </div>
                        <div class="col-sm-2">
                            <TrueFormValidationMessage For="@(new FieldIdentifier(FormModel, propertyInfo.Name))" />
                        </div>
                    </div>
                </Template>
            </FormItem>
        }

    </FormItems>
    <FormButtons>
        <TelerikButton ButtonType="ButtonType.Submit">@SubmitButtonLabel</TelerikButton>
    </FormButtons>
</TelerikForm>

<br/>
@ScreenLog

Here's the OnValueChanged (which needs work, but the get the gist of it):

protected void OnValueChanged(object e, string propertyName)
    {
        var propertyInfo = FormInputModel.GetType().GetProperty(propertyName);
        if (propertyInfo == null) return;

        if (propertyInfo.PropertyType == typeof(int))
            SetFormValue(propertyInfo.Name, Convert.ToInt32(e));

        if (propertyInfo.PropertyType == typeof(long))
            SetFormValue(propertyInfo.Name, Convert.ToInt64(e));

        if (propertyInfo.PropertyType == typeof(decimal))
        {
            // Needs work
            SetFormValue(propertyInfo.Name, Convert.ToDecimal(e, new CultureInfo("nl-NL", false)));
        }

        if (propertyInfo.PropertyType == typeof(string))
            SetFormValue(propertyInfo.Name, e.ToString());

        //if (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(DateTime?))
        //    throw new NotImplementedException("DateTime nog niet geimplementeerd.");

        //if (propertyInfo.PropertyType == typeof(bool) || propertyInfo.PropertyType == typeof(bool))
        //    throw new NotImplementedException("Boolean nog niet geimplementeerd.");
    }

And some of the helper methods:

private FormField GetFormField(string propertyName) =>
        FormDefinition.FormFields.SingleOrDefault(x => x.PropertyName == propertyName);

    protected string GetStringValue(string propertyName) =>
        GetFormValue(propertyName)?.ToString();

    protected decimal GetDecimalValue(string propertyName) =>
        Convert.ToDecimal(GetFormValue(propertyName));

    protected DateTime GetDateTimeValue(string propertyName) =>
        Convert.ToDateTime(GetFormValue(propertyName));

    protected object GetFormValue(string propertyName) =>
        FormModel?.GetType().GetProperty(propertyName)?.GetValue(FormModel);

    protected void SetFormValue(string propertyName, object value) =>
        FormModel.GetType().GetProperty(propertyName)?.SetValue(FormModel, value, null);
Any help is highly appreciated.
Jim
Top achievements
Rank 1
Iron
Iron
 updated question on 14 Dec 2022
0 answers
223 views

Good Morning,

Is it possible to launch PDF Viewer with "Fit to width" zoom?

Thanks

Klayton
Top achievements
Rank 1
Veteran
 asked on 13 Dec 2022
1 answer
217 views

In my opinion there is an error in the date picker component.

When I enter manually a date below the min value then the binded value is set to null but in the input field the wrong date is not cleared

(See Screenshot).

You can reproduce it by the DatePicker - Overview Demo:

1. Enter 01.01.1950

2. Click on the year and use the arrow down button to set 1949. The value is set to null and the input field is cleared.

3. Click on dd and set day to 01. Value 01.01.1949 is shown.

4. Click outside the date picker. Value is null but input shows 01.01.1949.

Please let me know whether this is a known issue and you intend to fix it.

 

Dimo
Telerik team
 answered on 13 Dec 2022
1 answer
215 views

When printing from the print button on the PDF viewer (on Edge or Chrome), the print is inconsistent with printing the downloaded document.

It seems to be printing an image of a web page containing the PDF rather an actually printing the PDF document

In particular, when printing from the button:

  • The print quality is visibly lower (printing is more gray than black).
  • The document extents are smaller on the page.
  • In some cases, the document prints across more pages than the actual PDF (e.g. 3 pages, when document is 1 page)
  • If Chrome header options are on, the page shows web page printing headers and footers as well.

Is this intended behaviour, or are there ways to modify this?

We'd like to use the control, as it removes the need for JavaScript code used in our current control, but the print quality probably precludes using it at the moment.

Thanks

Tim McMaster

Dimo
Telerik team
 answered on 13 Dec 2022
1 answer
179 views

Hi,

I have some additional Controls in a PanelBar-Header-Template. Anytime  when I click on a Control the Event bubbles to the PanelbarHeader and opens the panel.

How can I prevent this bubbeling?

As you see,  I tried to get rid of that with "stopPropagation". But you can imagine it doesn't work.

<TelerikPanelBar Data="@item.ColorsWithArticles"
                            @bind-ExpandedItems="@ExpandedItems"
                            OnExpand="@OnPanelBarExpand">
                            <PanelBarBindings>
                                <PanelBarBinding Level="0" ItemsField="Articles">
                                    <HeaderTemplate>
                                        @{
                                            var article = ((ColorAndArticlesItemVm)context);
                                            <div class="d-flex flex-row flex-wrap">
                                                <div class="p-1 border">
                                                    <img src="@article.Color.ColorImageUrl" alt="" style="width:28px; height:28px">
                                                </div>
                                                    <div onkeydown="stopPropagation(event)" onmousedown="stopPropagation(event)">
                                                        <TelerikTextBox @bind-Value="@StringValue" />
                                                    </div>
                                                </div>
<script suppress-error="BL9992">
    function stopPropagation(e) {
        e.stopPropagation();
    }
</script>

 

Thank you

Yanislav
Telerik team
 answered on 13 Dec 2022
1 answer
129 views

Hello,

I have to mark some text in the TelerikEditor and I'm using span for that:

Anamnese <span class='code-marked' title='S52, S60, S63'>robert</span>, 14 år, V=50 

I would like to have the "title" displayed as a tooltip but when I inspect the element in the TelerikEditor the "title" is removed.

Is there a way to use css or something similar to add tooltip for words in TelerikEditor? Is there any way to keep the "title" ?

TIA

Lucian

 

Nadezhda Tacheva
Telerik team
 answered on 13 Dec 2022
1 answer
309 views

I'm trying out the TelerikPdfViewer component in my Net7 Blazor Wasm project, but text PDFs open up with text that has missing parts.

Sample:

If I change the scale and change back using the zoom in/out buttons, the problem is fixed.  But the garbled text appears at each first viewing for all scale stops.

I get this in Chrome and in Edge, browser scaling at 100%, desktop scaling at 100% but alternate scaling for both also still have the same issue.

Dimo
Telerik team
 answered on 12 Dec 2022
1 answer
797 views

I followed How to set up in Blazor application | Telerik Reporting

All done! But when it run i got an error "telerikWebReportDesignerInterop.js 404"

<script src="_content/telerik.webreportdesigner.blazor/telerikWebReportDesignerInterop.js" defer></script>

and in console show:

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
      Unhandled exception in circuit 'Gyr8E4g_PwQHf7G2UKY2h3obMpujPfFp2x9i7dkB4oQ'.
      Microsoft.JSInterop.JSException: Could not find 'telerikWebReportDesignerInterop.createWebReportDesignerWidget' ('telerikWebReportDesignerInterop' was undefined).
Error: Could not find 'telerikWebReportDesignerInterop.createWebReportDesignerWidget' ('telerikWebReportDesignerInterop' was undefined).
    at https://localhost:5001/_framework/blazor.server.js:1:67713
    at Array.forEach (<anonymous>)
    at e.findFunction (https://localhost:5001/_framework/blazor.server.js:1:67673)
    at v (https://localhost:5001/_framework/blazor.server.js:1:69415)
    at https://localhost:5001/_framework/blazor.server.js:1:70361
    at new Promise (<anonymous>)
    at e.beginInvokeJSFromDotNet (https://localhost:5001/_framework/blazor.server.js:1:70334)
    at https://localhost:5001/_framework/blazor.server.js:1:26441
    at Array.forEach (<anonymous>)
    at e.invokeClientMethod (https://localhost:5001/_framework/blazor.server.js:1:26411)
         at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
         at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

Telerik Reporting 15.1.21.616

<PackageReference Include="Telerik.Reporting.Services.AspNetCore" Version="15.1.21.616" />
<PackageReference Include="Telerik.Reporting.OpenXmlRendering" Version="15.1.21.616" />
<PackageReference Include="Telerik.ReportViewer.Blazor" Version="15.1.21.616" />
<PackageReference Include="Telerik.WebReportDesigner.Blazor" Version="15.1.21.616" />
<PackageReference Include="Telerik.WebReportDesigner.Services" Version="15.1.21.616" />

Startup.cs


namespace CSharp.Net5.BlazorIntegrationDemo
{
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.DependencyInjection.Extensions;
    using Microsoft.Extensions.Hosting;
    using System;
    using Telerik.Reporting.Cache.File;
    using Telerik.Reporting.Services;
    using Telerik.WebReportDesigner.Services;

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            this.Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddRazorPages()
                .AddNewtonsoftJson();
            services.AddServerSideBlazor();

            // Configure dependencies for ReportsController.
            services.TryAddSingleton<IReportServiceConfiguration>(sp =>
                new ReportServiceConfiguration
                {
                    ReportingEngineConfiguration = sp.GetService<IConfiguration>(),
                    HostAppId = "Net5BlazorDemo",
                    Storage = new FileStorage(),
                    ReportSourceResolver = new UriReportSourceResolver(System.IO.Path.Combine(sp.GetService<IWebHostEnvironment>().ContentRootPath, "..", "..", "..", "..", "Report Designer", "Examples")),
                });

            // Configure dependencies for ReportDesignerController.
            services.TryAddSingleton<IReportDesignerServiceConfiguration>(sp => new ReportDesignerServiceConfiguration
            {
                DefinitionStorage = new FileDefinitionStorage(
                    System.IO.Path.Combine(sp.GetService<IWebHostEnvironment>().ContentRootPath, "..", "..", "..", "..", "Report Designer", "Examples")),
                SettingsStorage = new FileSettingsStorage(
                    System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Telerik Reporting")),
                ResourceStorage = new ResourceStorage(
                    System.IO.Path.Combine(sp.GetService<IWebHostEnvironment>().ContentRootPath, "..", "..", "..", "..", "Report Designer", "Examples", "Resources"))
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }

        /// <summary>
        /// Loads a reporting configuration from a specific JSON-based configuration file.
        /// </summary>
        /// <param name="environment">The current web hosting environment used to obtain the content root path</param>
        /// <returns>IConfiguration instance used to initialize the Reporting engine</returns>
        static IConfiguration ResolveSpecificReportingConfiguration(IWebHostEnvironment environment)
        {
            // If a specific configuration needs to be passed to the reporting engine, add it through a new IConfiguration instance.
            var reportingConfigFileName = System.IO.Path.Combine(environment.ContentRootPath, "reportingAppSettings.json");
            return new ConfigurationBuilder()
                .AddJsonFile(reportingConfigFileName, true)
                .Build();
        }
    }
}

WebReportDesignerDemo.razor


@page "/webreportdesigner"
@using Telerik.WebReportDesigner.Blazor

<style>
    #wrd1 {
        position: relative;
        height: 880px;
        padding-right: 50px;
    }
</style>

@* Create the WebReportDesignerWidget *@

<p>This Web Report Designer instance works with reports hosted locally using the Reporting REST service. For more information, visit the <a target="_blank" href="https://docs.telerik.com/reporting/web-report-designer">Web Report Designer</a> article.</p>
<WebReportDesigner DesignerId="wrd1"
                   ServiceUrl="/api/reportdesigner"
                   Report="Dashboard.trdp"
                   ToolboxArea="new ToolboxAreaOptions() { Layout = ToolboxAreaLayout.List }"
                   PropertiesArea="new PropertiesAreaOptions() { Layout = PropertiesAreaLayout.Categorized }" />

Steve
Top achievements
Rank 2
Iron
 updated answer on 09 Dec 2022
0 answers
349 views

I a working on an app that has x number of steps depending user configuration.   I need to be able to call a service to load the model that I pass into the step.  However, I have not had any success finding an event.  I also tried using the OnInitialized within the component inside the step.  But that has not worked either.  Is there any event which will fire before the parameter is passed to the component within the step. 

Note: I really want contain it within the step because I have no way of know which steps are available without some complex logic.

 

            @if (WizardInfo.ShowIncomeDetails)
            {
               <WizardStep Label=@IncomeDetailsText
                            IconClass="fa-regular fa-calendar-day fa-xl"
                            OnChange="@IncomeDetailsOnChangeAsync"
                            Valid="@incomeDetailsIsValid">
                    <Content>
                        <TelerikForm Model="IncomeDetails"
                                 ValidationMessageType="@FormValidationMessageType.Inline">
                            <FormValidation>
                                <FluentValidationValidator></FluentValidationValidator>
                                <ValidationSummary/>
                            </FormValidation>
                            <FormItems>
                                <IncomeDetails IncomeDetails="@IncomeDetails" @ref="@incomeDetailsComponent" />
                            </FormItems>
                            <FormButtons></FormButtons>
                        </TelerikForm>
                   </Content>
                </WizardStep>
}
Matt
Top achievements
Rank 1
 asked on 09 Dec 2022
Narrow your results
Selected tags
Tags
+? more
Top users last month
Jay
Top achievements
Rank 3
Bronze
Iron
Iron
yw
Top achievements
Rank 2
Iron
Iron
Stefan
Top achievements
Rank 2
Iron
Iron
Iron
Kao Hung
Top achievements
Rank 1
Iron
Bohdan
Top achievements
Rank 2
Iron
Iron
Iron
Want to show your ninja superpower to fellow developers?
Top users last month
Jay
Top achievements
Rank 3
Bronze
Iron
Iron
yw
Top achievements
Rank 2
Iron
Iron
Stefan
Top achievements
Rank 2
Iron
Iron
Iron
Kao Hung
Top achievements
Rank 1
Iron
Bohdan
Top achievements
Rank 2
Iron
Iron
Iron
Want to show your ninja superpower to fellow developers?
Want to show your ninja superpower to fellow developers?