init
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import manifest from '../manifest';
|
||||
import usePlugin from '../utils/usePlugin';
|
||||
|
||||
const RootComponent: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
const plugin = usePlugin();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Add your root component content here */}
|
||||
<p>Template Plugin Root Component</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RootComponent
|
||||
@@ -0,0 +1 @@
|
||||
// Add your global styles here
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getPluginAssetsPath } from '../utils/utils';
|
||||
|
||||
export const basePath = getPluginAssetsPath();
|
||||
|
||||
// Add your assets configuration here
|
||||
export type AssetsType = {
|
||||
// Define your asset types
|
||||
}
|
||||
|
||||
const assets: AssetsType = {
|
||||
// Add your assets here
|
||||
} as const
|
||||
|
||||
export default assets
|
||||
@@ -0,0 +1,5 @@
|
||||
import Svgs from './svgs.js';
|
||||
|
||||
export {
|
||||
Svgs,
|
||||
};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = {
|
||||
icon: string,
|
||||
className?: string,
|
||||
} & React.HTMLAttributes<HTMLElement>
|
||||
|
||||
export default function CompassIcon({icon, className, ...rest}: Props): JSX.Element {
|
||||
// All compass icon classes start with icon,
|
||||
// so not expecting that prefix in props.
|
||||
return (
|
||||
<i
|
||||
className={`CompassIcon icon-${icon} ${className}`}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { PluginRegistry } from 'loop-plugin-sdk';
|
||||
import { Store, Action } from 'redux';
|
||||
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||
import 'loop-plugin-sdk/window'
|
||||
import manifest from './manifest';
|
||||
import registerApp from './registerApp';
|
||||
|
||||
export default class Plugin {
|
||||
// @ts-ignore
|
||||
public store: Store;
|
||||
|
||||
// @ts-ignore
|
||||
public registry: PluginRegistry
|
||||
|
||||
public onStoreChanged: any;
|
||||
public uninitialize: any;
|
||||
|
||||
public async initialize(registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>) {
|
||||
this.store = store;
|
||||
this.registry = registry;
|
||||
|
||||
await registerApp(this, registry, store)
|
||||
}
|
||||
}
|
||||
|
||||
window.registerPlugin(manifest.id, new Plugin());
|
||||
@@ -0,0 +1,49 @@
|
||||
import { PluginRegistry } from 'loop-plugin-sdk';
|
||||
import { getCurrentUserLocale } from 'loop-plugin-sdk/loop/redux/selectors/entities/i18n';
|
||||
import { Action, Store } from 'redux';
|
||||
import RootComponent from './components/RootComponent';
|
||||
import Plugin from './index';
|
||||
import manifest from './manifest';
|
||||
import reducer from './store/reducers';
|
||||
import * as React from 'react';
|
||||
import { getTranslations } from './utils/utils';
|
||||
import { Provider, useSelector } from 'react-redux';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||
|
||||
export const pluginStoreId = `plugins-${manifest.id}`;
|
||||
|
||||
export default async function Initialize(plugin: Plugin, registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>) {
|
||||
const Providers: React.FC<React.PropsWithChildren<any>> = ({ children }) => {
|
||||
const locale = useSelector((state) => getCurrentUserLocale(state as GlobalState) || 'en');
|
||||
return (
|
||||
<IntlProvider locale={locale} key={locale} messages={getTranslations(locale)}>
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const regIds: string[] = [];
|
||||
|
||||
registry.registerReducer(reducer);
|
||||
registry.registerTranslations(getTranslations);
|
||||
|
||||
plugin.uninitialize = () => {
|
||||
// Add cleanup logic here
|
||||
};
|
||||
|
||||
const reinit = () => {
|
||||
if (regIds.length > 0) {
|
||||
regIds.forEach(regId => registry.unregisterComponent(regId));
|
||||
regIds.splice(0, regIds.length);
|
||||
}
|
||||
|
||||
regIds.push(registry.registerRootComponent(() => (
|
||||
<Providers>
|
||||
<RootComponent />
|
||||
</Providers>
|
||||
)));
|
||||
};
|
||||
|
||||
reinit();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DispatchFunc } from '../types/store';
|
||||
import { ACTIONS } from './reducers';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// Add your action creators here
|
||||
export function exampleAction(data: string) {
|
||||
return ((dispatch: DispatchFunc) => {
|
||||
dispatch({
|
||||
type: ACTIONS.EXAMPLE_ACTION,
|
||||
data: { example: data }
|
||||
});
|
||||
}) as unknown as AnyAction;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { PluginStore } from '../types/store';
|
||||
|
||||
export type ActionType = {
|
||||
type: ACTIONS
|
||||
data: PluginActionData
|
||||
}
|
||||
|
||||
export enum ACTIONS {
|
||||
// Add your action types here
|
||||
EXAMPLE_ACTION = 'EXAMPLE_ACTION',
|
||||
}
|
||||
|
||||
export type PluginActionData = {
|
||||
// Add your action data types here
|
||||
example?: string;
|
||||
}
|
||||
|
||||
const EmptyState: PluginStore = {
|
||||
// Add your initial state here
|
||||
}
|
||||
|
||||
function pluginState(state: PluginStore = EmptyState, { type, data }: ActionType): PluginStore {
|
||||
switch (type) {
|
||||
case ACTIONS.EXAMPLE_ACTION:
|
||||
return {
|
||||
...state,
|
||||
// Handle action
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
pluginState,
|
||||
});
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
import Plugin from '../index';
|
||||
|
||||
declare module "*.module.css";
|
||||
declare module "*.module.scss";
|
||||
|
||||
declare global {
|
||||
const __PLUGIN_DEV__: boolean;
|
||||
const __PLUGIN_COMPONENTS_HOST__: string | null;
|
||||
|
||||
interface Window {
|
||||
plugins: Record<string, Plugin | unknown>;
|
||||
basename: string;
|
||||
desktopAPI?: any;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||
import { ActionType } from '../store/reducers';
|
||||
|
||||
export type PluginStore = {
|
||||
// Add your store state here
|
||||
}
|
||||
|
||||
export type GlobalStatePlugin = GlobalState & {
|
||||
[name: string]: {
|
||||
pluginState: PluginStore
|
||||
}
|
||||
}
|
||||
|
||||
export type GetStateType = () => GlobalStatePlugin
|
||||
|
||||
export type DispatchFunc = (action: ActionType) => any;
|
||||
@@ -0,0 +1 @@
|
||||
// trackEvent: (event: Telemetry.Event, source: Telemetry.Source, props?: Record<string, string>) => void,
|
||||
@@ -0,0 +1,39 @@
|
||||
import Client4 from 'loop-plugin-sdk/loop/client/client4';
|
||||
import manifest from '../manifest';
|
||||
|
||||
export type StdApiResp<T = undefined> = {
|
||||
status: string
|
||||
error?: string
|
||||
data?: T
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
client = new Client4();
|
||||
|
||||
async exampleRequest(): Promise<StdApiResp<{ message: string }>> {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return await this.client.doFetch<StdApiResp<{ message: string }>>(`/plugins/${manifest.id}/example`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {status: 'error', error: error as string};
|
||||
}
|
||||
}
|
||||
|
||||
async apiPingServerStatus(): Promise<boolean> {
|
||||
try {
|
||||
const response = await this.client.ping();
|
||||
return response.status === 'OK';
|
||||
} catch (err) {
|
||||
console.error('Ошибка пинга сервера:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const apiClient = new ApiClient();
|
||||
|
||||
export default apiClient
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getTheme } from 'loop-plugin-sdk/loop/redux/selectors/entities/preferences';
|
||||
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||
import React, { useEffect } from "react";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { isDarkTheme } from './colorUtils';
|
||||
|
||||
const useIsDarkTheme = () => {
|
||||
const [isDark, setIsDark] = React.useState(false);
|
||||
const theme = useSelector((state: GlobalState) => getTheme(state))
|
||||
|
||||
useEffect(() => {
|
||||
setIsDark(isDarkTheme());
|
||||
}, [theme])
|
||||
|
||||
return isDark
|
||||
}
|
||||
|
||||
export default useIsDarkTheme
|
||||
@@ -0,0 +1,6 @@
|
||||
import Plugin from '../index';
|
||||
import manifest from '../manifest';
|
||||
|
||||
export default function usePlugin() {
|
||||
return window.plugins[manifest.id] as Plugin
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { WebSocketClient } from 'loop-plugin-sdk/loop/client';
|
||||
import { DispatchFunc } from 'loop-plugin-sdk/loop/redux/types/actions';
|
||||
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||
import { AnyAction } from 'redux';
|
||||
import en from '../../i18n/en.json';
|
||||
import ru from '../../i18n/ru.json';
|
||||
import Plugin from '../index';
|
||||
import manifest from '../manifest';
|
||||
import { pluginStoreId } from '../registerApp';
|
||||
import { GlobalStatePlugin } from '../types/store';
|
||||
|
||||
export function getPluginAssetsPath() {
|
||||
return `${window.basename || ''}/plugins/${manifest.id}/assets`;
|
||||
}
|
||||
|
||||
export function getPlugin(): Plugin {
|
||||
return window.plugins[manifest.id] as Plugin;
|
||||
}
|
||||
|
||||
export function isDesktopApp(): boolean {
|
||||
return window.navigator.userAgent.indexOf('Electron') !== -1;
|
||||
}
|
||||
|
||||
export function getWebappUtils() {
|
||||
let utils;
|
||||
try {
|
||||
utils = window.opener ? window.opener.WebappUtils : window.WebappUtils;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return utils;
|
||||
}
|
||||
|
||||
export type ProductApi = {
|
||||
selectRhsPost: (postId: string) => (dispatch: DispatchFunc) => AnyAction
|
||||
closeRhs: () => (dispatch: DispatchFunc) => AnyAction
|
||||
getIsRhsOpen: (state: GlobalState) => boolean
|
||||
getRhsSelectedPostId: (state: GlobalState) => string
|
||||
useWebSocketClient: () => WebSocketClient
|
||||
}
|
||||
|
||||
export function getProductApi(): ProductApi {
|
||||
let utils: any;
|
||||
try {
|
||||
// @ts-ignore
|
||||
utils = window.opener ? window.opener.ProductApi : window['ProductApi'];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return utils;
|
||||
}
|
||||
|
||||
export function getPluginStoreFromState(state: GlobalStatePlugin) {
|
||||
return state[pluginStoreId].pluginState;
|
||||
}
|
||||
|
||||
export function getTranslations(locale: string) {
|
||||
switch (locale) {
|
||||
case "en":
|
||||
return en;
|
||||
case "ru":
|
||||
return ru;
|
||||
}
|
||||
return en;
|
||||
}
|
||||
|
||||
export function getRandomInt(min: number, max: number) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export async function waitForServer(
|
||||
pingFn: () => Promise<any>,
|
||||
interval = 2000,
|
||||
maxAttempts = 10
|
||||
): Promise<void> {
|
||||
let attempts = 0;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const check = async () => {
|
||||
attempts++;
|
||||
const ok = await pingFn();
|
||||
if (ok) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
reject(new Error("Не удалось подключиться после нескольких попыток"));
|
||||
return;
|
||||
}
|
||||
setTimeout(check, interval);
|
||||
};
|
||||
check();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user