diff --git a/rsconcept/frontend/src/app/Router.tsx b/rsconcept/frontend/src/app/Router.tsx index 62028d26..da3548ea 100644 --- a/rsconcept/frontend/src/app/Router.tsx +++ b/rsconcept/frontend/src/app/Router.tsx @@ -6,6 +6,7 @@ import LibraryPage from '@/pages/LibraryPage'; import LoginPage from '@/pages/LoginPage'; import ManualsPage from '@/pages/ManualsPage'; import NotFoundPage from '@/pages/NotFoundPage'; +import OssPage from '@/pages/OssPage'; import PasswordChangePage from '@/pages/PasswordChangePage'; import RegisterPage from '@/pages/RegisterPage'; import RestorePasswordPage from '@/pages/RestorePasswordPage'; @@ -57,6 +58,10 @@ export const Router = createBrowserRouter([ path: `${routes.rsforms}/:id`, element: }, + { + path: `${routes.oss}/:id`, + element: + }, { path: routes.manuals, element: diff --git a/rsconcept/frontend/src/app/backendAPI.ts b/rsconcept/frontend/src/app/backendAPI.ts index c5c099cc..77163451 100644 --- a/rsconcept/frontend/src/app/backendAPI.ts +++ b/rsconcept/frontend/src/app/backendAPI.ts @@ -7,8 +7,17 @@ import { toast } from 'react-toastify'; import { type ErrorData } from '@/components/info/InfoError'; import { ILexemeData, IResolutionData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language'; -import { ILibraryItem, ILibraryUpdateData, ITargetAccessPolicy, ITargetLocation, IVersionData } from '@/models/library'; +import { + AccessPolicy, + ILibraryItem, + ILibraryUpdateData, + ITargetAccessPolicy, + ITargetLocation, + IVersionData, + LibraryItemType +} from '@/models/library'; import { ILibraryCreateData } from '@/models/library'; +import { IOperationSchemaData } from '@/models/oss'; import { IConstituentaList, IConstituentaMeta, @@ -224,6 +233,29 @@ export function postCloneLibraryItem(target: string, request: FrontExchange) { + request.onSuccess({ + id: Number(target), + comment: '123', + alias: 'oss1', + access_policy: AccessPolicy.PUBLIC, + editors: [], + owner: 1, + item_type: LibraryItemType.OSS, + location: '/U', + read_only: false, + subscribers: [], + time_create: '0', + time_update: '0', + title: 'TestOss', + visible: false + }); + // AxiosGet({ + // endpoint: `/api/oss/${target}`, // TODO: endpoint to access OSS + // request: request + // }); +} + export function getRSFormDetails(target: string, version: string, request: FrontPull) { if (!version) { AxiosGet({ diff --git a/rsconcept/frontend/src/app/urls.ts b/rsconcept/frontend/src/app/urls.ts index a2ff397c..87465098 100644 --- a/rsconcept/frontend/src/app/urls.ts +++ b/rsconcept/frontend/src/app/urls.ts @@ -17,7 +17,8 @@ export const routes = { create_schema: 'library/create', manuals: 'manuals', help: 'manuals', - rsforms: 'rsforms' + rsforms: 'rsforms', + oss: 'oss' }; interface SchemaProps { @@ -27,6 +28,11 @@ interface SchemaProps { active?: number | string; } +interface OssProps { + id: number | string; + tab: number; +} + /** * Internal navigation URLs. */ @@ -49,5 +55,8 @@ export const urls = { const versionStr = version !== undefined ? `v=${version}&` : ''; const activeStr = active !== undefined ? `&active=${active}` : ''; return `/rsforms/${id}?${versionStr}tab=${tab}${activeStr}`; + }, + oss_props: ({ id, tab }: OssProps) => { + return `/oss/${id}?tab=${tab}`; } }; diff --git a/rsconcept/frontend/src/context/OssContext.tsx b/rsconcept/frontend/src/context/OssContext.tsx new file mode 100644 index 00000000..41385511 --- /dev/null +++ b/rsconcept/frontend/src/context/OssContext.tsx @@ -0,0 +1,275 @@ +'use client'; + +import { createContext, useCallback, useContext, useMemo, useState } from 'react'; + +import { + type DataCallback, + deleteUnsubscribe, + patchEditorsSet as patchSetEditors, + patchLibraryItem, + patchSetAccessPolicy, + patchSetLocation, + patchSetOwner, + postSubscribe +} from '@/app/backendAPI'; +import { type ErrorData } from '@/components/info/InfoError'; +import useOssDetails from '@/hooks/useOssDetails'; +import { AccessPolicy, ILibraryItem } from '@/models/library'; +import { ILibraryUpdateData } from '@/models/library'; +import { IOperationSchema } from '@/models/oss'; +import { UserID } from '@/models/user'; + +import { useAuth } from './AuthContext'; +import { useLibrary } from './LibraryContext'; + +interface IOssContext { + schema?: IOperationSchema; + itemID: string; + + loading: boolean; + errorLoading: ErrorData; + processing: boolean; + processingError: ErrorData; + + isOwned: boolean; + isSubscribed: boolean; + + update: (data: ILibraryUpdateData, callback?: DataCallback) => void; + + subscribe: (callback?: () => void) => void; + unsubscribe: (callback?: () => void) => void; + setOwner: (newOwner: UserID, callback?: () => void) => void; + setAccessPolicy: (newPolicy: AccessPolicy, callback?: () => void) => void; + setLocation: (newLocation: string, callback?: () => void) => void; + setEditors: (newEditors: UserID[], callback?: () => void) => void; +} + +const OssContext = createContext(null); +export const useOSS = () => { + const context = useContext(OssContext); + if (context === null) { + throw new Error('useOSS has to be used within '); + } + return context; +}; + +interface OssStateProps { + itemID: string; + children: React.ReactNode; +} + +export const OssState = ({ itemID, children }: OssStateProps) => { + const library = useLibrary(); + const { user } = useAuth(); + const { + schema: schema, // prettier: split lines + error: errorLoading, + setSchema, + loading + } = useOssDetails({ + target: itemID + }); + const [processing, setProcessing] = useState(false); + const [processingError, setProcessingError] = useState(undefined); + + const [toggleTracking, setToggleTracking] = useState(false); + + const isOwned = useMemo(() => { + return user?.id === schema?.owner || false; + }, [user, schema?.owner]); + + const isSubscribed = useMemo(() => { + if (!user || !schema || !user.id) { + return false; + } + return schema.subscribers.includes(user.id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user, schema, toggleTracking]); + + const update = useCallback( + (data: ILibraryUpdateData, callback?: DataCallback) => { + if (!schema) { + return; + } + setProcessingError(undefined); + patchLibraryItem(itemID, { + data: data, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: newData => { + setSchema(Object.assign(schema, newData)); + library.localUpdateItem(newData); + if (callback) callback(newData); + } + }); + }, + [itemID, setSchema, schema, library] + ); + + const subscribe = useCallback( + (callback?: () => void) => { + if (!schema || !user) { + return; + } + setProcessingError(undefined); + postSubscribe(itemID, { + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + if (user.id && !schema.subscribers.includes(user.id)) { + schema.subscribers.push(user.id); + } + if (!user.subscriptions.includes(schema.id)) { + user.subscriptions.push(schema.id); + } + setToggleTracking(prev => !prev); + if (callback) callback(); + } + }); + }, + [itemID, schema, user] + ); + + const unsubscribe = useCallback( + (callback?: () => void) => { + if (!schema || !user) { + return; + } + setProcessingError(undefined); + deleteUnsubscribe(itemID, { + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + if (user.id && schema.subscribers.includes(user.id)) { + schema.subscribers.splice(schema.subscribers.indexOf(user.id), 1); + } + if (user.subscriptions.includes(schema.id)) { + user.subscriptions.splice(user.subscriptions.indexOf(schema.id), 1); + } + setToggleTracking(prev => !prev); + if (callback) callback(); + } + }); + }, + [itemID, schema, user] + ); + + const setOwner = useCallback( + (newOwner: UserID, callback?: () => void) => { + if (!schema) { + return; + } + setProcessingError(undefined); + patchSetOwner(itemID, { + data: { + user: newOwner + }, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + schema.owner = newOwner; + library.localUpdateItem(schema); + if (callback) callback(); + } + }); + }, + [itemID, schema, library] + ); + + const setAccessPolicy = useCallback( + (newPolicy: AccessPolicy, callback?: () => void) => { + if (!schema) { + return; + } + setProcessingError(undefined); + patchSetAccessPolicy(itemID, { + data: { + access_policy: newPolicy + }, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + schema.access_policy = newPolicy; + library.localUpdateItem(schema); + if (callback) callback(); + } + }); + }, + [itemID, schema, library] + ); + + const setLocation = useCallback( + (newLocation: string, callback?: () => void) => { + if (!schema) { + return; + } + setProcessingError(undefined); + patchSetLocation(itemID, { + data: { + location: newLocation + }, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + schema.location = newLocation; + library.localUpdateItem(schema); + if (callback) callback(); + } + }); + }, + [itemID, schema, library] + ); + + const setEditors = useCallback( + (newEditors: UserID[], callback?: () => void) => { + if (!schema) { + return; + } + setProcessingError(undefined); + patchSetEditors(itemID, { + data: { + users: newEditors + }, + showError: true, + setLoading: setProcessing, + onError: setProcessingError, + onSuccess: () => { + schema.editors = newEditors; + if (callback) callback(); + } + }); + }, + [itemID, schema] + ); + + return ( + + {children} + + ); +}; diff --git a/rsconcept/frontend/src/context/RSFormContext.tsx b/rsconcept/frontend/src/context/RSFormContext.tsx index 471b1e23..c61757a1 100644 --- a/rsconcept/frontend/src/context/RSFormContext.tsx +++ b/rsconcept/frontend/src/context/RSFormContext.tsx @@ -54,7 +54,7 @@ import { useLibrary } from './LibraryContext'; interface IRSFormContext { schema?: IRSForm; - schemaID: string; + itemID: string; versionID?: string; loading: boolean; @@ -105,12 +105,12 @@ export const useRSForm = () => { }; interface RSFormStateProps { - schemaID: string; + itemID: string; versionID?: string; children: React.ReactNode; } -export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) => { +export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) => { const library = useLibrary(); const { user } = useAuth(); const { @@ -120,7 +120,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) setSchema, loading } = useRSFormDetails({ - target: schemaID, + target: itemID, version: versionID }); const [processing, setProcessing] = useState(false); @@ -148,7 +148,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchLibraryItem(schemaID, { + patchLibraryItem(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -160,7 +160,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, setSchema, schema, library] + [itemID, setSchema, schema, library] ); const upload = useCallback( @@ -169,7 +169,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchUploadTRS(schemaID, { + patchUploadTRS(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -181,7 +181,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, setSchema, schema, library] + [itemID, setSchema, schema, library] ); const subscribe = useCallback( @@ -190,7 +190,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - postSubscribe(schemaID, { + postSubscribe(itemID, { showError: true, setLoading: setProcessing, onError: setProcessingError, @@ -206,7 +206,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, user] + [itemID, schema, user] ); const unsubscribe = useCallback( @@ -215,7 +215,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - deleteUnsubscribe(schemaID, { + deleteUnsubscribe(itemID, { showError: true, setLoading: setProcessing, onError: setProcessingError, @@ -231,7 +231,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, user] + [itemID, schema, user] ); const setOwner = useCallback( @@ -240,7 +240,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchSetOwner(schemaID, { + patchSetOwner(itemID, { data: { user: newOwner }, @@ -254,7 +254,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, library] + [itemID, schema, library] ); const setAccessPolicy = useCallback( @@ -263,7 +263,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchSetAccessPolicy(schemaID, { + patchSetAccessPolicy(itemID, { data: { access_policy: newPolicy }, @@ -277,7 +277,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, library] + [itemID, schema, library] ); const setLocation = useCallback( @@ -286,7 +286,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchSetLocation(schemaID, { + patchSetLocation(itemID, { data: { location: newLocation }, @@ -300,7 +300,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, library] + [itemID, schema, library] ); const setEditors = useCallback( @@ -309,7 +309,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchSetEditors(schemaID, { + patchSetEditors(itemID, { data: { users: newEditors }, @@ -322,7 +322,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema] + [itemID, schema] ); const resetAliases = useCallback( @@ -331,7 +331,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchResetAliases(schemaID, { + patchResetAliases(itemID, { showError: true, setLoading: setProcessing, onError: setProcessingError, @@ -342,7 +342,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, library, user, setSchema] + [itemID, schema, library, user, setSchema] ); const restoreOrder = useCallback( @@ -351,7 +351,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) return; } setProcessingError(undefined); - patchRestoreOrder(schemaID, { + patchRestoreOrder(itemID, { showError: true, setLoading: setProcessing, onError: setProcessingError, @@ -362,13 +362,13 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, schema, library, user, setSchema] + [itemID, schema, library, user, setSchema] ); const produceStructure = useCallback( (data: ITargetCst, callback?: DataCallback) => { setProcessingError(undefined); - patchProduceStructure(schemaID, { + patchProduceStructure(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -380,26 +380,26 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [setSchema, library, schemaID] + [setSchema, library, itemID] ); const download = useCallback( (callback: DataCallback) => { setProcessingError(undefined); - getTRSFile(schemaID, String(schema?.version ?? ''), { + getTRSFile(itemID, String(schema?.version ?? ''), { showError: true, setLoading: setProcessing, onError: setProcessingError, onSuccess: callback }); }, - [schemaID, schema] + [itemID, schema] ); const cstCreate = useCallback( (data: ICstCreateData, callback?: DataCallback) => { setProcessingError(undefined); - postNewConstituenta(schemaID, { + postNewConstituenta(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -411,13 +411,13 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, library, setSchema] + [itemID, library, setSchema] ); const cstDelete = useCallback( (data: IConstituentaList, callback?: () => void) => { setProcessingError(undefined); - patchDeleteConstituenta(schemaID, { + patchDeleteConstituenta(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -429,7 +429,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [schemaID, library, setSchema] + [itemID, library, setSchema] ); const cstUpdate = useCallback( @@ -442,18 +442,18 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) onError: setProcessingError, onSuccess: newData => reload(setProcessing, () => { - library.localUpdateTimestamp(Number(schemaID)); + library.localUpdateTimestamp(Number(itemID)); if (callback) callback(newData); }) }); }, - [schemaID, library, reload] + [itemID, library, reload] ); const cstRename = useCallback( (data: ICstRenameData, callback?: DataCallback) => { setProcessingError(undefined); - patchRenameConstituenta(schemaID, { + patchRenameConstituenta(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -465,13 +465,13 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [setSchema, library, schemaID] + [setSchema, library, itemID] ); const cstSubstitute = useCallback( (data: ICstSubstituteData, callback?: () => void) => { setProcessingError(undefined); - patchSubstituteConstituents(schemaID, { + patchSubstituteConstituents(itemID, { data: data, showError: true, setLoading: setProcessing, @@ -483,43 +483,43 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) } }); }, - [setSchema, library, schemaID] + [setSchema, library, itemID] ); const cstMoveTo = useCallback( (data: ICstMovetoData, callback?: () => void) => { setProcessingError(undefined); - patchMoveConstituenta(schemaID, { + patchMoveConstituenta(itemID, { data: data, showError: true, setLoading: setProcessing, onError: setProcessingError, onSuccess: newData => { setSchema(newData); - library.localUpdateTimestamp(Number(schemaID)); + library.localUpdateTimestamp(Number(itemID)); if (callback) callback(); } }); }, - [schemaID, library, setSchema] + [itemID, library, setSchema] ); const versionCreate = useCallback( (data: IVersionData, callback?: (version: number) => void) => { setProcessingError(undefined); - postCreateVersion(schemaID, { + postCreateVersion(itemID, { data: data, showError: true, setLoading: setProcessing, onError: setProcessingError, onSuccess: newData => { setSchema(newData.schema); - library.localUpdateTimestamp(Number(schemaID)); + library.localUpdateTimestamp(Number(itemID)); if (callback) callback(newData.version); } }); }, - [schemaID, library, setSchema] + [itemID, library, setSchema] ); const versionUpdate = useCallback( @@ -592,19 +592,19 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps) onError: setProcessingError, onSuccess: newData => { setSchema(newData); - library.localUpdateTimestamp(Number(schemaID)); + library.localUpdateTimestamp(Number(itemID)); if (callback) callback(newData); } }); }, - [library, schemaID, setSchema] + [library, itemID, setSchema] ); return ( (undefined); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(undefined); + + function setSchema(data?: IOperationSchemaData) { + if (!data) { + setInner(undefined); + return; + } + const newSchema = new OssLoader(data).produceOSS(); + setInner(newSchema); + } + + const reload = useCallback( + (setCustomLoading?: typeof setLoading, callback?: () => void) => { + setError(undefined); + if (!target) { + return; + } + getOssDetails(target, { + showError: true, + setLoading: setCustomLoading ?? setLoading, + onError: error => { + setInner(undefined); + setError(error); + }, + onSuccess: schema => { + setSchema(schema); + if (callback) callback(); + } + }); + }, + [target] + ); + + useEffect(() => { + reload(); + }, [reload]); + + return { schema, setSchema, reload, error, setError, loading }; +} + +export default useOssDetails; diff --git a/rsconcept/frontend/src/models/OssLoader.ts b/rsconcept/frontend/src/models/OssLoader.ts new file mode 100644 index 00000000..2e3d0a09 --- /dev/null +++ b/rsconcept/frontend/src/models/OssLoader.ts @@ -0,0 +1,23 @@ +/** + * Module: OSS data loading and processing. + */ + +import { IOperationSchema, IOperationSchemaData } from './oss'; + +/** + * Loads data into an {@link IOperationSchema} based on {@link IOperationSchemaData}. + * + */ +export class OssLoader { + private schema: IOperationSchemaData; + + constructor(input: IOperationSchemaData) { + this.schema = input; + } + + produceOSS(): IOperationSchema { + const result = this.schema as IOperationSchema; + result.producedData = [1, 2, 3]; // TODO: put data processing here + return result; + } +} diff --git a/rsconcept/frontend/src/models/library.ts b/rsconcept/frontend/src/models/library.ts index 6864c9d4..e0f5ec4c 100644 --- a/rsconcept/frontend/src/models/library.ts +++ b/rsconcept/frontend/src/models/library.ts @@ -75,15 +75,39 @@ export interface ILibraryItem { } /** - * Represents library item extended data. + * Represents library item constant data loaded for both OSS and RSForm. */ -export interface ILibraryItemEx extends ILibraryItem { +export interface ILibraryItemData extends ILibraryItem { subscribers: UserID[]; editors: UserID[]; +} + +/** + * Represents library item extended data with versions. + */ +export interface ILibraryItemVersioned extends ILibraryItemData { version?: VersionID; versions: IVersionInfo[]; } +/** + * Represents common library item editor controller. + */ +export interface ILibraryItemEditor { + schema?: ILibraryItemData; + + isMutable: boolean; + isProcessing: boolean; + + setOwner: (newOwner: UserID) => void; + setAccessPolicy: (newPolicy: AccessPolicy) => void; + promptEditors: () => void; + promptLocation: () => void; + toggleSubscribe: () => void; + + share: () => void; +} + /** * Represents update data for editing {@link ILibraryItem}. */ diff --git a/rsconcept/frontend/src/models/oss.ts b/rsconcept/frontend/src/models/oss.ts new file mode 100644 index 00000000..f1a706e1 --- /dev/null +++ b/rsconcept/frontend/src/models/oss.ts @@ -0,0 +1,23 @@ +/** + * Module: Schema of Synthesis Operations. + */ + +import { ILibraryItemData } from './library'; +import { UserID } from './user'; + +/** + * Represents backend data for Schema of Synthesis Operations. + */ +export interface IOperationSchemaData extends ILibraryItemData { + additional_data?: number[]; +} + +/** + * Represents Schema of Synthesis Operations. + */ +export interface IOperationSchema extends IOperationSchemaData { + subscribers: UserID[]; + editors: UserID[]; + + producedData: number[]; // TODO: modify this to store calculated state on load +} diff --git a/rsconcept/frontend/src/models/rsform.ts b/rsconcept/frontend/src/models/rsform.ts index 140c66db..428e8326 100644 --- a/rsconcept/frontend/src/models/rsform.ts +++ b/rsconcept/frontend/src/models/rsform.ts @@ -4,7 +4,7 @@ import { Graph } from '@/models/Graph'; -import { ILibraryItem, ILibraryItemEx, LibraryItemID } from './library'; +import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library'; import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang'; /** @@ -226,7 +226,7 @@ export interface IRSFormStats { /** * Represents formal explication for set of concepts. */ -export interface IRSForm extends ILibraryItemEx { +export interface IRSForm extends ILibraryItemVersioned { items: IConstituenta[]; stats: IRSFormStats; graph: Graph; @@ -237,7 +237,7 @@ export interface IRSForm extends ILibraryItemEx { /** * Represents data for {@link IRSForm} provided by backend. */ -export interface IRSFormData extends ILibraryItemEx { +export interface IRSFormData extends ILibraryItemVersioned { items: IConstituentaData[]; } diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx index c5283534..06fb79df 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ViewLibrary.tsx @@ -15,7 +15,7 @@ import { useConceptOptions } from '@/context/OptionsContext'; import { useUsers } from '@/context/UsersContext'; import useLocalStorage from '@/hooks/useLocalStorage'; import useWindowSize from '@/hooks/useWindowSize'; -import { ILibraryItem } from '@/models/library'; +import { ILibraryItem, LibraryItemType } from '@/models/library'; import { storage } from '@/utils/constants'; interface ViewLibraryProps { @@ -33,7 +33,11 @@ function ViewLibrary({ items, resetQuery }: ViewLibraryProps) { const [itemsPerPage, setItemsPerPage] = useLocalStorage(storage.libraryPagination, 50); function handleOpenItem(item: ILibraryItem, event: CProps.EventMouse) { - router.push(urls.schema(item.id), event.ctrlKey || event.metaKey); + if (item.item_type === LibraryItemType.RSFORM) { + router.push(urls.schema(item.id), event.ctrlKey || event.metaKey); + } else if (item.item_type === LibraryItemType.OSS) { + router.push(urls.oss(item.id), event.ctrlKey || event.metaKey); + } } const windowSize = useWindowSize(); diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/HelpAccess.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/HelpAccess.tsx index 44897b7f..36d1b547 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/HelpAccess.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/HelpAccess.tsx @@ -1,7 +1,7 @@ function HelpAccess() { return (
-

Организация доступов к схемам

+

Организация доступов

TBD.

); diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx new file mode 100644 index 00000000..6933f56f --- /dev/null +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/EditorOssCard.tsx @@ -0,0 +1,62 @@ +'use client'; + +import clsx from 'clsx'; + +import FlexColumn from '@/components/ui/FlexColumn'; +import AnimateFade from '@/components/wrap/AnimateFade'; +import { useAuth } from '@/context/AuthContext'; +import { useOSS } from '@/context/OssContext'; +import EditorLibraryItem from '@/pages/RSFormPage/EditorRSFormCard/EditorLibraryItem'; +import { globals } from '@/utils/constants'; + +import { useOssEdit } from '../OssEditContext'; +import FormOSS from './FormOSS'; +import RSFormToolbar from './OssFormToolbar'; + +interface EditorOssCardProps { + isModified: boolean; + setIsModified: React.Dispatch>; + onDestroy: () => void; +} + +function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardProps) { + const { schema, isSubscribed } = useOSS(); + const { user } = useAuth(); + const controller = useOssEdit(); + + function initiateSubmit() { + const element = document.getElementById(globals.library_item_editor) as HTMLFormElement; + if (element) { + element.requestSubmit(); + } + } + + function handleInput(event: React.KeyboardEvent) { + if ((event.ctrlKey || event.metaKey) && event.code === 'KeyS') { + if (isModified) { + initiateSubmit(); + } + event.preventDefault(); + } + } + + return ( + <> + + + + + + + + + ); +} + +export default EditorOssCard; diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx new file mode 100644 index 00000000..e7556797 --- /dev/null +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssCard/FormOSS.tsx @@ -0,0 +1,140 @@ +'use client'; + +import clsx from 'clsx'; +import { useEffect, useLayoutEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +import { IconSave } from '@/components/Icons'; +import SubmitButton from '@/components/ui/SubmitButton'; +import TextArea from '@/components/ui/TextArea'; +import TextInput from '@/components/ui/TextInput'; +import { useOSS } from '@/context/OssContext'; +import { ILibraryUpdateData, LibraryItemType } from '@/models/library'; +import AccessToolbar from '@/pages/RSFormPage/EditorRSFormCard/AccessToolbar'; +import { limits, patterns } from '@/utils/constants'; + +import { useOssEdit } from '../OssEditContext'; + +interface FormOSSProps { + id?: string; + isModified: boolean; + setIsModified: React.Dispatch>; +} + +function FormOSS({ id, isModified, setIsModified }: FormOSSProps) { + const { schema, update, processing } = useOSS(); + const controller = useOssEdit(); + + const [title, setTitle] = useState(''); + const [alias, setAlias] = useState(''); + const [comment, setComment] = useState(''); + const [visible, setVisible] = useState(false); + const [readOnly, setReadOnly] = useState(false); + + useEffect(() => { + if (!schema) { + setIsModified(false); + return; + } + setIsModified( + schema.title !== title || + schema.alias !== alias || + schema.comment !== comment || + schema.visible !== visible || + schema.read_only !== readOnly + ); + return () => setIsModified(false); + }, [ + schema, + schema?.title, + schema?.alias, + schema?.comment, + schema?.visible, + schema?.read_only, + title, + alias, + comment, + visible, + readOnly, + setIsModified + ]); + + useLayoutEffect(() => { + if (schema) { + setTitle(schema.title); + setAlias(schema.alias); + setComment(schema.comment); + setVisible(schema.visible); + setReadOnly(schema.read_only); + } + }, [schema]); + + const handleSubmit = (event?: React.FormEvent) => { + if (event) { + event.preventDefault(); + } + const data: ILibraryUpdateData = { + item_type: LibraryItemType.RSFORM, + title: title, + alias: alias, + comment: comment, + visible: visible, + read_only: readOnly + }; + update(data, () => toast.success('Изменения сохранены')); + }; + + return ( +
+ setTitle(event.target.value)} + /> +
+ setAlias(event.target.value)} + /> + setVisible(prev => !prev)} + readOnly={readOnly} + toggleReadOnly={() => setReadOnly(prev => !prev)} + controller={controller} + /> +
+ +