npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

oip-common

v0.11.0

Published

A template for cross-platform web applications based on sakai-ng and primeNG

Readme

OipCommon

Add assets in angular.json

{
  "glob": "**/*",
  "input": "node_modules/oip-common/assets",
  "output": "/assets"
}

Add tailwind config

const primeui = require("tailwindcss-primeui");
module.exports = {
  /* Your config */
  content: [, /* Your config */ "./node_modules/oip-common/**/*.{html,ts,scss,css,js,mjs}"]
  /* Your config */
};

Add scss

@use "../../../node_modules/oip-common/assets/oip-common";

Init L10nService to AppComponent

import { Component, inject, OnInit } from "@angular/core";
import { SecurityService } from "oip-common";
import { RouterOutlet } from "@angular/router";
import { ToastModule } from "primeng/toast";
import { L10nService } from "../../../oip-common/src/services/l10n.service";

@Component({
  selector: "app-root",
  template: `
    <p-toast />
    <router-outlet></router-outlet>
  `,
  standalone: true,
  imports: [ToastModule, RouterOutlet]
})
export class AppComponent implements OnInit {
  private readonly securityService = inject(SecurityService);
  private readonly translateService = inject(L10nService);

  ngOnInit() {
    this.securityService.auth();
    this.translateService.init([
      {
        code: "en",
        name: "English",
        icon: "flag flag-gb"
      },
      {
        code: "ru",
        name: "Русский",
        icon: "flag flag-ru"
      }
    ]);
  }
}

Set up routing

provideOipRoutes builds the standard route tree: an authenticated shell (AppLayoutComponent guarded by AuthGuardService and moduleAccessGuard) holding your routes and the built-in pages, followed by the unauthorized, notfound and ** routes. It keeps ** last, so do not append anything after the returned array.

import { oipAuthGuard, provideOipRoutes } from "oip-common";

export const appRoutes = provideOipRoutes({
  children: [
    {
      path: "dashboard/:id",
      loadComponent: () => import("./app/components/dashboard/dashboard.component").then((m) => m.DashboardComponent),
      canActivate: [oipAuthGuard]
    }
  ]
});

Use oipAuthGuard on every route of your own that requires a signed in user.

All built-in routes are registered by default. Pass false to drop one, or a string to move it to another path — the path must keep the same route parameters:

provideOipRoutes({
  children: [
    /* ... */
  ],
  features: {
    dbMigration: "rtds-meta-data-context-migration-module/:id",
    modules: false
  }
});

| features key | Default path | Notes | | -------------- | ------------------------------ | -------------------------------------------------- | | access | access | Redirect target of both guards, keep it registered | | error | error | | | profile | profile | | | config | config | | | applications | applications | Administrators only | | modules | modules | Administrators only | | discussion | discussion/:id | | | dbMigration | db-migration/:id | | | iframeModule | iframe-module/:id | | | extensions | extensions/:extensionKey/:id | | | noModules | no-modules | Shown when the user has no module available | | start | `` (the shell root) | Redirects to the start module, see below |

The remaining options: layout replaces the shell component, rootRoutes adds routes outside the shell, notFoundPath and unauthorizedPath rename those two pages, and wildcard: false drops the ** route when your application registers its own catch-all.

Open a module by default

The empty path is claimed by a redirect route that resolves the module instance the user lands on. It picks, in order: the startRoute of the host application, the module the user marked as their start page, the first module of their menu, and finally the no-modules page. The menu returned by the backend is already filtered by rights, so the resolved module is always one the user may open — a module that was deleted or whose rights were revoked simply drops out and the next candidate wins.

provideOipRoutes({
  children: [
    /* ... */
  ],
  startRoute: "/dashboard/1"
});

Leave startRoute unset to follow the user's own choice. Users pick it from the sidebar: right click a menu item and choose Set as start page, which stores it per user on the backend and marks the item with a star. The route is skipped when children already declares its own empty path, and features: { start: false } drops it entirely.

StartPageService backs all of this — resolveStartUrl() returns the target as a UrlTree (or null when nothing is available), setStartModule(id) and clearStartModule() change the choice. Results are cached per user and dropped when the session or the choice changes.

Every built-in route is also exported on its own — oipConfigRoute, oipModulesRoute and so on — for applications that assemble the route tree by hand instead of calling provideOipRoutes.

Block the UI during module transitions

AppLayoutComponent renders BlockLoaderComponent, a full screen blocker driven by ModuleLoadingService. It covers router navigation on its own, plus the module bootstrap that BaseModuleComponent runs after NavigationEnd — rights, settings, onModuleInstanceChange() and extension loading — so nothing is clickable while a module is still coming up. Applications using the standard shell get it for free; only a custom layout component has to render <block-loader />, and it must sit outside .layout-wrapper, which the blocker marks inert while it is visible.

Register your own long running work with ModuleLoadingService when it must block the same way:

import { Component, inject } from "@angular/core";
import { BaseModuleComponent, ModuleLoadingService } from "oip-common";

@Component({
  /* ... */
})
export class ReportModuleComponent extends BaseModuleComponent<ReportSettings, void> {
  private readonly moduleLoadingService = inject(ModuleLoadingService);

  protected async runReport(): Promise<void> {
    await this.moduleLoadingService.track(this.reportApi.build(this.id));
  }
}

track() releases the blocker when the promise rejects, so an error never leaves the UI locked. The begin() / end() pair is available for work that is not a promise; balance it from a finally block. A begin() that is never released is dropped after 30 seconds with a console warning.

The blocker appears only when a transition lasts longer than 150 ms and then stays for at least 300 ms, so fast navigation does not flash a spinner.