Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@benev/slate

Package Overview
Dependencies
Maintainers
1
Versions
74
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@benev/slate - npm Package Compare versions

Comparing version 0.0.0-dev.4 to 0.0.0-dev.5

s/element/part/mixin_setups.ts

8

package.json
{
"name": "@benev/slate",
"version": "0.0.0-dev.4",
"version": "0.0.0-dev.5",
"description": "frontend web stuff",

@@ -26,3 +26,3 @@ "license": "MIT",

"@benev/turtle": "^0.5.0",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-node-resolve": "^15.2.2",
"chokidar": "^3.5.3",

@@ -35,4 +35,4 @@ "chokidar-cli": "^3.0.0",

"npm-run-all": "^4.1.5",
"rollup": "^3.29.1",
"terser": "^5.19.4",
"rollup": "^4.0.0",
"terser": "^5.21.0",
"typescript": "^5.2.2"

@@ -39,0 +39,0 @@ },

@@ -24,5 +24,5 @@

1. install slate into your project
1. install slate (and lit) into your project
```sh
npm i @benev/slate
npm i @benev/slate lit
```

@@ -34,3 +34,3 @@ 1. establish a "context" for your app

export class Context extends BaseContext {
export class Context implements BaseContext {

@@ -83,3 +83,3 @@ // state management system

// component auto rerenders on state changes
#increment => () => {
#increment = () => {
this.#state.count++

@@ -129,3 +129,3 @@ }

#increment => () => {
#increment = () => {
this.#state.count++

@@ -132,0 +132,0 @@ }

@@ -44,11 +44,11 @@

case String:
return raw
return raw ?? undefined
case Number:
return raw && Number(raw)
return raw !== null
? Number(raw)
: undefined
case Boolean:
return raw
? raw !== "false"
: false
return raw !== null

@@ -55,0 +55,0 @@ default:

export class MetallicElement extends HTMLElement {
import {mixinSetups} from "./mixin_setups.js"
#setups = new Set<() => () => void>()
.add(() => this.setup())
export class MetallicElement extends mixinSetups(HTMLElement) {}
#setdowns = new Set<() => void>()
register_setup(setup: () => () => void) {
this.#setups.add(setup)
}
setup() {
return () => {}
}
connectedCallback() {
for (const setup of this.#setups)
this.#setdowns.add(setup())
}
disconnectedCallback() {
for (const setdown of this.#setdowns)
setdown()
this.#setdowns.clear()
}
}

@@ -31,3 +31,4 @@

export * from "./view/shale.js"
export * from "./view/clay.js"
export * from "./view/parts/types.js"

@@ -6,6 +6,7 @@

import {Flat} from "../flatstate/flat.js"
import {ClayViewClass} from "../view/clay.js"
import {apply} from "../base/helpers/apply.js"
import {View} from "../view/parts/types.js"
import {BaseElementClass} from "../base/element.js"
import {ShaleViewClass, shale_view} from "../view/shale.js"
import {ShaleView, ShaleViewClass} from "../view/shale.js"
import {LightView, View, ViewParams} from "../view/parts/types.js"
import {requirement, RequirementGroup, RequirementGroupProvided} from "../tools/requirement.js"

@@ -17,30 +18,38 @@

export const prepare_frontend = <C extends BaseContext>() => {
export const prepare_frontend = <C extends BaseContext>() => ({
component: <E extends BaseElementClass>(fun: (context: C) => E) => fun,
return ({
component: <E extends BaseElementClass>(fun: (context: C) => E) => fun,
components: <E extends RequirementGroup<C, BaseElementClass>>(
context: C,
elements: E
) => (
Pipe.with(elements)
.to(requirement.provide(context))
.to(apply.flat(context.flat))
.to(apply.theme(context.theme))
.done() as RequirementGroupProvided<E>
),
components: <E extends RequirementGroup<C, BaseElementClass>>(
context: C,
elements: E
) => (
Pipe.with(elements)
.to(requirement.provide(context))
.to(apply.flat(context.flat))
.to(apply.theme(context.theme))
.done() as RequirementGroupProvided<E>
),
view: <V extends (ShaleViewClass | ClayViewClass)>(
fun: (context: C) => V
) => (context: C): V extends ShaleViewClass ? View<ViewParams<V>> : LightView<ViewParams<V>> => {
view: <V extends ShaleViewClass>(fun: (context: C) => V) => (context: C) => shale_view({
View: fun(context),
flat: context.flat,
theme: context.theme,
}),
const {flat, theme} = context
const UnknownView = fun(context)
views: <V extends RequirementGroup<C, View<any>>>(
context: C,
viewgroup: V,
) => requirement.provide(context)(viewgroup),
})
}
if (UnknownView.prototype instanceof ShaleView) {
const View = UnknownView as ShaleViewClass
return View.directive(View, {flat, theme}) as any
}
else {
const View = UnknownView as ClayViewClass
return View.directive(View, {flat}) as any
}
},
views: <V extends RequirementGroup<C, View<any> | LightView<any>>>(
context: C,
viewgroup: V,
) => requirement.provide(context)(viewgroup),
})
import {css, html} from "lit"
import {ClayView} from "./view/clay.js"
import {Flat} from "./flatstate/flat.js"

@@ -17,10 +19,2 @@ import {ShaleView} from "./view/shale.js"

export const MyComponent = component(_ => class extends GoldElement {
render() {
}
})
export type MyComponentClass = ComponentClass<typeof MyComponent>
export type MyComponentInstance = ComponentInstance<typeof MyComponent>
export const MyView = view(context => class extends ShaleView {

@@ -38,2 +32,8 @@ static name = "my-view"

export const MyClay = view(context => class extends ClayView {
render(greeting: string) {
return html`<p>${greeting}</p>`
}
})
export const MyView2 = view(context => class extends ShaleView {

@@ -74,2 +74,20 @@ static name = "my-view-2"

export const MyComponent = component(context => class extends GoldElement {
#views = views(context, {
MyView,
MyView2,
MyClay,
})
render() {
return html`
${this.#views.MyView({props: [123]})}
${this.#views.MyClay("hello")}
`
}
})
export type MyComponentClass = ComponentClass<typeof MyComponent>
export type MyComponentInstance = ComponentInstance<typeof MyComponent>
const context = new Context()

@@ -76,0 +94,0 @@

@@ -5,4 +5,14 @@

import {ViewUse} from "./use.js"
import {BaseView} from "./base_view.js"
import {Flat} from "../../flatstate/flat.js"
export type ViewClass = {
new(...p: any[]): BaseView
directive: (...args: any[]) => any
}
export type ViewParams<V extends ViewClass> = (
Parameters<InstanceType<V>["render"]>
)
export type ViewAttributes = {

@@ -38,4 +48,10 @@ [key: string]: string | number | boolean | undefined

export type View<P extends any[]> = (data: ViewInputs<P>) => (TemplateResult | void)
export type View<P extends any[]> = (
(data: ViewInputs<P>) => (TemplateResult | void)
)
export type LightView<P extends any[]> = (
(...props: P) => (TemplateResult | void)
)
export type ViewHooksSetupDetails<R> = {

@@ -42,0 +58,0 @@ result: R

import {AsyncDirective} from "lit/async-directive.js"
import {CSSResultGroup, Part, TemplateResult} from "lit"
import {CSSResultGroup, Part, TemplateResult, css} from "lit"
import {Flat} from "../flatstate/flat.js"
import {BaseView} from "./parts/base_view.js"
import {make_view_root} from "./parts/root.js"
import {ViewInputs, View} from "./parts/types.js"
import {AsyncDirective} from "lit/async-directive.js"
import {apply_details} from "./parts/apply_details.js"
import {custom_directive_with_detail_input} from "./parts/custom_directive_with_detail_input.js"
import {View, ViewInputs, ViewParams} from "./parts/types.js"
import {custom_directive_with_detail_input} from "./parts/custom_directives.js"
export abstract class ShaleView {
static name: string
static styles: CSSResultGroup
abstract render(...args: any[]): TemplateResult | void
export type ShaleViewClass = ({
new(...p: ConstructorParameters<typeof ShaleView>): ShaleView
} & typeof ShaleView)
export abstract class ShaleView extends BaseView {
static name = "unknown"
static styles: CSSResultGroup = css``
default_auto_exportparts = true
#root: ReturnType<typeof make_view_root>

@@ -21,2 +25,3 @@ #rerender: () => void

constructor(root: ReturnType<typeof make_view_root>, rerender: () => void) {
super()
this.#root = root

@@ -29,71 +34,78 @@ this.#rerender = rerender

requestUpdate() { this.#rerender() }
}
abstract render(...props: any[]): TemplateResult | void
export type ShaleViewClass = {
new(...params: ConstructorParameters<typeof ShaleView>): ShaleView
name: string
styles: CSSResultGroup
}
static directive<V extends ShaleViewClass>(
View: ShaleViewClass,
{flat, theme}: {
flat: Flat
theme: CSSResultGroup
},
) {
export function shale_view<V extends ShaleViewClass>({flat, theme, View}: {
flat: Flat
theme: CSSResultGroup
View: V
}): View<Parameters<InstanceType<V>["render"]>> {
type P = ViewParams<V>
type P = Parameters<InstanceType<V>["render"]>
return custom_directive_with_detail_input(class extends AsyncDirective {
#recent_input?: ViewInputs<P>
#rerender = () => {
if (this.#recent_input)
this.setValue(
this.#root.render_into_shadow(
this.render(this.#recent_input!)
return custom_directive_with_detail_input(class extends AsyncDirective {
#recent_input?: ViewInputs<P>
#rerender = () => {
if (this.#recent_input)
this.setValue(
this.#root.render_into_shadow(
this.render(this.#recent_input!)
)
)
)
}
#stop: (() => void) | undefined
#root = make_view_root(View.name, [theme, View.styles])
#view = new View(this.#root, this.#rerender)
}
#stop: (() => void) | undefined
#root = make_view_root(View.name, [theme, View.styles])
#view = new View(this.#root, this.#rerender)
update(_: Part, props: [ViewInputs<P>]) {
return this.#root.render_into_shadow(this.render(...props))
}
constructor(...args: ConstructorParameters<typeof AsyncDirective>) {
super(...args)
this.#view.connectedCallback()
}
render(input: ViewInputs<P>) {
apply_details(this.#root.container, input, this.#recent_input)
this.#recent_input = input
this.#root.auto_exportparts = (
input.auto_exportparts ?? this.#view.default_auto_exportparts
)
update(_: Part, props: [ViewInputs<P>]) {
return this.#root.render_into_shadow(this.render(...props))
}
if (this.#stop)
this.#stop()
render(input: ViewInputs<P>) {
apply_details(this.#root.container, input, this.#recent_input)
this.#recent_input = input
this.#root.auto_exportparts = (
input.auto_exportparts ?? this.#view.default_auto_exportparts
)
let result: TemplateResult | void = undefined
if (this.#stop)
this.#stop()
this.#stop = flat.manual({
debounce: true,
discover: false,
collector: () => {
const props = this.#recent_input!.props
result = this.#view.render(...props)
},
responder: () => {
this.#rerender()
},
})
let result: TemplateResult | void = undefined
return result
}
this.#stop = flat.manual({
debounce: true,
discover: false,
collector: () => {
const props = this.#recent_input!.props
result = this.#view.render(...props)
},
responder: () => {
this.#rerender()
},
})
disconnected() {
if (this.#stop) {
this.#stop()
this.#stop = undefined
return result
}
}
}) as View<P>
disconnected() {
this.#view.disconnectedCallback()
if (this.#stop) {
this.#stop()
this.#stop = undefined
}
}
reconnected() {
this.#view.connectedCallback()
}
}) as View<P>
}
}

@@ -10,9 +10,9 @@ import { ShaleView } from "../../view/shale.js";

case String:
return raw;
return raw ?? undefined;
case Number:
return raw && Number(raw);
return raw !== null
? Number(raw)
: undefined;
case Boolean:
return raw
? raw !== "false"
: false;
return raw !== null;
default:

@@ -19,0 +19,0 @@ throw new Error(`invalid attribute type for "${name}"`);

@@ -1,7 +0,17 @@

export declare class MetallicElement extends HTMLElement {
#private;
register_setup(setup: () => () => void): void;
setup(): () => void;
connectedCallback(): void;
disconnectedCallback(): void;
declare const MetallicElement_base: {
new (...args: any[]): {
[x: string]: any;
"__#7@#setups": Set<() => () => void>;
"__#7@#setdowns": Set<() => void>;
register_setup(setup: () => () => void): void;
setup(): () => void;
connectedCallback(): void;
disconnectedCallback(): void;
};
} & {
new (): HTMLElement;
prototype: HTMLElement;
};
export declare class MetallicElement extends MetallicElement_base {
}
export {};

@@ -1,21 +0,4 @@

export class MetallicElement extends HTMLElement {
#setups = new Set()
.add(() => this.setup());
#setdowns = new Set();
register_setup(setup) {
this.#setups.add(setup);
}
setup() {
return () => { };
}
connectedCallback() {
for (const setup of this.#setups)
this.#setdowns.add(setup());
}
disconnectedCallback() {
for (const setdown of this.#setdowns)
setdown();
this.#setdowns.clear();
}
import { mixinSetups } from "./mixin_setups.js";
export class MetallicElement extends mixinSetups(HTMLElement) {
}
//# sourceMappingURL=metallic.js.map

@@ -24,2 +24,3 @@ export * from "./base/addons/attributes.js";

export * from "./view/shale.js";
export * from "./view/clay.js";
export * from "./view/parts/types.js";

@@ -24,3 +24,4 @@ export * from "./base/addons/attributes.js";

export * from "./view/shale.js";
export * from "./view/clay.js";
export * from "./view/parts/types.js";
//# sourceMappingURL=index.js.map
import { CSSResultGroup } from "lit";
import { Flat } from "../flatstate/flat.js";
import { View } from "../view/parts/types.js";
import { ClayViewClass } from "../view/clay.js";
import { BaseElementClass } from "../base/element.js";
import { ShaleViewClass } from "../view/shale.js";
import { LightView, View, ViewParams } from "../view/parts/types.js";
import { RequirementGroup, RequirementGroupProvided } from "../tools/requirement.js";

@@ -16,4 +17,4 @@ export type BaseContext = {

components: <E_1 extends RequirementGroup<C, BaseElementClass>>(context: C, elements: E_1) => RequirementGroupProvided<E_1>;
view: <V extends ShaleViewClass>(fun: (context: C) => V) => (context: C) => View<Parameters<InstanceType<V>["render"]>>;
views: <V_1 extends RequirementGroup<C, View<any>>>(context: C, viewgroup: V_1) => RequirementGroupProvided<V_1>;
view: <V extends ShaleViewClass | ClayViewClass>(fun: (context: C) => V) => (context: C) => V extends ShaleViewClass ? View<ViewParams<V>> : LightView<ViewParams<V>>;
views: <V_1 extends RequirementGroup<C, View<any> | LightView<any>>>(context: C, viewgroup: V_1) => RequirementGroupProvided<V_1>;
};
import { Pipe } from "../tools/pipe.js";
import { apply } from "../base/helpers/apply.js";
import { shale_view } from "../view/shale.js";
import { ShaleView } from "../view/shale.js";
import { requirement } from "../tools/requirement.js";
export const prepare_frontend = () => {
return ({
component: (fun) => fun,
components: (context, elements) => Pipe.with(elements)
.to(requirement.provide(context))
.to(apply.flat(context.flat))
.to(apply.theme(context.theme))
.done(),
view: (fun) => (context) => shale_view({
View: fun(context),
flat: context.flat,
theme: context.theme,
}),
views: (context, viewgroup) => requirement.provide(context)(viewgroup),
});
};
export const prepare_frontend = () => ({
component: (fun) => fun,
components: (context, elements) => Pipe.with(elements)
.to(requirement.provide(context))
.to(apply.flat(context.flat))
.to(apply.theme(context.theme))
.done(),
view: (fun) => (context) => {
const { flat, theme } = context;
const UnknownView = fun(context);
if (UnknownView.prototype instanceof ShaleView) {
const View = UnknownView;
return View.directive(View, { flat, theme });
}
else {
const View = UnknownView;
return View.directive(View, { flat });
}
},
views: (context, viewgroup) => requirement.provide(context)(viewgroup),
});
//# sourceMappingURL=frontend.js.map

@@ -7,5 +7,14 @@ import { Flat } from "./flatstate/flat.js";

}
export declare const MyView: (context: Context) => import("./index.js").View<[count: number]>;
export declare const MyClay: (context: Context) => import("./index.js").LightView<[greeting: string]>;
export declare const MyView2: (context: Context) => import("./index.js").View<[]>;
export declare const MyComponent: (context: Context) => {
new (): {
render(): void;
[x: string]: any;
"__#18@#views": import("./index.js").RequirementGroupProvided<{
MyView: (context: Context) => import("./index.js").View<[count: number]>;
MyView2: (context: Context) => import("./index.js").View<[]>;
MyClay: (context: Context) => import("./index.js").LightView<[greeting: string]>;
}>;
render(): import("lit-html").TemplateResult<1>;
"__#12@#root": ShadowRoot;

@@ -24,4 +33,4 @@ "__#12@#init"?: {

connectedCallback(): void;
"__#11@#setups": Set<() => () => void>;
"__#11@#setdowns": Set<() => void>;
"__#7@#setups": Set<() => () => void>;
"__#7@#setdowns": Set<() => void>;
register_setup(setup: () => () => void): void;

@@ -357,3 +366,1 @@ setup(): () => void;

export type MyComponentInstance = ComponentInstance<typeof MyComponent>;
export declare const MyView: (context: Context) => import("./index.js").View<[count: number]>;
export declare const MyView2: (context: Context) => import("./index.js").View<[]>;
import { css, html } from "lit";
import { ClayView } from "./view/clay.js";
import { Flat } from "./flatstate/flat.js";

@@ -13,6 +14,2 @@ import { ShaleView } from "./view/shale.js";

const { component, components, view, views } = prepare_frontend();
export const MyComponent = component(_ => class extends GoldElement {
render() {
}
});
export const MyView = view(context => class extends ShaleView {

@@ -28,2 +25,7 @@ static name = "my-view";

});
export const MyClay = view(context => class extends ClayView {
render(greeting) {
return html `<p>${greeting}</p>`;
}
});
export const MyView2 = view(context => class extends ShaleView {

@@ -58,4 +60,17 @@ static name = "my-view-2";

});
export const MyComponent = component(context => class extends GoldElement {
#views = views(context, {
MyView,
MyView2,
MyClay,
});
render() {
return html `
${this.#views.MyView({ props: [123] })}
${this.#views.MyClay("hello")}
`;
}
});
const context = new Context();
register_to_dom(components(context, { MyComponent }));
//# sourceMappingURL=sketch.js.map
import { CSSResultGroup, TemplateResult } from "lit";
import { ViewUse } from "./use.js";
import { BaseView } from "./base_view.js";
import { Flat } from "../../flatstate/flat.js";
export type ViewClass = {
new (...p: any[]): BaseView;
directive: (...args: any[]) => any;
};
export type ViewParams<V extends ViewClass> = (Parameters<InstanceType<V>["render"]>);
export type ViewAttributes = {

@@ -27,3 +33,4 @@ [key: string]: string | number | boolean | undefined;

};
export type View<P extends any[]> = (data: ViewInputs<P>) => (TemplateResult | void);
export type View<P extends any[]> = ((data: ViewInputs<P>) => (TemplateResult | void));
export type LightView<P extends any[]> = ((...props: P) => (TemplateResult | void));
export type ViewHooksSetupDetails<R> = {

@@ -30,0 +37,0 @@ result: R;

import { CSSResultGroup, TemplateResult } from "lit";
import { Flat } from "../flatstate/flat.js";
import { BaseView } from "./parts/base_view.js";
import { make_view_root } from "./parts/root.js";
import { View } from "./parts/types.js";
export declare abstract class ShaleView {
import { View, ViewParams } from "./parts/types.js";
export type ShaleViewClass = ({
new (...p: ConstructorParameters<typeof ShaleView>): ShaleView;
} & typeof ShaleView);
export declare abstract class ShaleView extends BaseView {
#private;
static name: string;
static styles: CSSResultGroup;
abstract render(...args: any[]): TemplateResult | void;
default_auto_exportparts: boolean;

@@ -15,12 +18,7 @@ constructor(root: ReturnType<typeof make_view_root>, rerender: () => void);

requestUpdate(): void;
abstract render(...props: any[]): TemplateResult | void;
static directive<V extends ShaleViewClass>(View: ShaleViewClass, { flat, theme }: {
flat: Flat;
theme: CSSResultGroup;
}): View<ViewParams<V>>;
}
export type ShaleViewClass = {
new (...params: ConstructorParameters<typeof ShaleView>): ShaleView;
name: string;
styles: CSSResultGroup;
};
export declare function shale_view<V extends ShaleViewClass>({ flat, theme, View }: {
flat: Flat;
theme: CSSResultGroup;
View: V;
}): View<Parameters<InstanceType<V>["render"]>>;

@@ -0,8 +1,10 @@

import { css } from "lit";
import { BaseView } from "./parts/base_view.js";
import { make_view_root } from "./parts/root.js";
import { AsyncDirective } from "lit/async-directive.js";
import { make_view_root } from "./parts/root.js";
import { apply_details } from "./parts/apply_details.js";
import { custom_directive_with_detail_input } from "./parts/custom_directive_with_detail_input.js";
export class ShaleView {
static name;
static styles;
import { custom_directive_with_detail_input } from "./parts/custom_directives.js";
export class ShaleView extends BaseView {
static name = "unknown";
static styles = css ``;
default_auto_exportparts = true;

@@ -12,2 +14,3 @@ #root;

constructor(root, rerender) {
super();
this.#root = root;

@@ -19,44 +22,52 @@ this.#rerender = rerender;

requestUpdate() { this.#rerender(); }
}
export function shale_view({ flat, theme, View }) {
return custom_directive_with_detail_input(class extends AsyncDirective {
#recent_input;
#rerender = () => {
if (this.#recent_input)
this.setValue(this.#root.render_into_shadow(this.render(this.#recent_input)));
};
#stop;
#root = make_view_root(View.name, [theme, View.styles]);
#view = new View(this.#root, this.#rerender);
update(_, props) {
return this.#root.render_into_shadow(this.render(...props));
}
render(input) {
apply_details(this.#root.container, input, this.#recent_input);
this.#recent_input = input;
this.#root.auto_exportparts = (input.auto_exportparts ?? this.#view.default_auto_exportparts);
if (this.#stop)
this.#stop();
let result = undefined;
this.#stop = flat.manual({
debounce: true,
discover: false,
collector: () => {
const props = this.#recent_input.props;
result = this.#view.render(...props);
},
responder: () => {
this.#rerender();
},
});
return result;
}
disconnected() {
if (this.#stop) {
this.#stop();
this.#stop = undefined;
static directive(View, { flat, theme }) {
return custom_directive_with_detail_input(class extends AsyncDirective {
#recent_input;
#rerender = () => {
if (this.#recent_input)
this.setValue(this.#root.render_into_shadow(this.render(this.#recent_input)));
};
#stop;
#root = make_view_root(View.name, [theme, View.styles]);
#view = new View(this.#root, this.#rerender);
constructor(...args) {
super(...args);
this.#view.connectedCallback();
}
}
});
update(_, props) {
return this.#root.render_into_shadow(this.render(...props));
}
render(input) {
apply_details(this.#root.container, input, this.#recent_input);
this.#recent_input = input;
this.#root.auto_exportparts = (input.auto_exportparts ?? this.#view.default_auto_exportparts);
if (this.#stop)
this.#stop();
let result = undefined;
this.#stop = flat.manual({
debounce: true,
discover: false,
collector: () => {
const props = this.#recent_input.props;
result = this.#view.render(...props);
},
responder: () => {
this.#rerender();
},
});
return result;
}
disconnected() {
this.#view.disconnectedCallback();
if (this.#stop) {
this.#stop();
this.#stop = undefined;
}
}
reconnected() {
this.#view.connectedCallback();
}
});
}
}
//# sourceMappingURL=shale.js.map

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc