/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2021, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2021 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import { IHttpClient } from "./IHttpClient"; import { IShortUserDescription } from "./IUser"; import { Role } from "./Role"; import { Member } from "./Member"; import { json, text, userFullName, userInitials } from "./impl/Utils"; /** * Projects are used to collaborate on models and track issues. */ export class Project { private _data: any; protected httpClient: IHttpClient; /** * @param data - An object that implements project data storage. * @param httpClient - Http client. */ constructor(data: any, httpClient: IHttpClient) { this.httpClient = httpClient; this.data = data; } protected internalGet(relativePath: string) { return this.httpClient.get(`/projects/${this.id}${relativePath}`); } protected internalPost( relativePath: string, body?: ArrayBuffer | Blob | globalThis.File | FormData | object | string | null ) { return this.httpClient.post(`/projects/${this.id}${relativePath}`, body); } protected internalPut( relativePath: string, body?: ArrayBuffer | Blob | globalThis.File | FormData | object | string | null ) { return this.httpClient.put(`/projects/${this.id}${relativePath}`, body); } protected internalDelete(relativePath: string) { return this.httpClient.delete(`/projects/${this.id}${relativePath}`); } /** * Project features the user has access to. * * @property {string[]} project_actions - Actions are allowed to be performed: `update`, * `createTopic`, `createDocument`. * @readonly */ get authorization(): any { return this.data.authorization; } /** * Project creation time (UTC) in the format specified in ISO 8601. * * @readonly */ get createdAt(): string { return this.data.createdAt; } /** * Project custom fields object, to store custom data. */ get customFields(): any { return this.data.customFields; } set customFields(value: any) { this.data.customFields = value; } /** * Raw project data received from the server. * * @readonly */ get data(): any { return this._data; } private set data(value: any) { this._data = value; this._data.previewUrl = value.avatarUrl ? `${this.httpClient.serverUrl}/projects/${this._data.id}/preview` : ""; this._data.owner.avatarUrl = `${this.httpClient.serverUrl}/users/${this._data.owner.userId}/avatar`; this._data.owner.fullName = userFullName(this._data.owner); this._data.owner.initials = userInitials(this._data.owner.fullName); } /** * Project description. */ get description(): string { return this.data.description; } set description(value: string) { this.data.description = value; } /** * Project end date in the format specified in ISO 8601. */ get endDate(): string { return this.data.endDate; } set endDate(value: string | Date) { this.data.endDate = value instanceof Date ? value.toISOString() : value; } /** * Unique project ID. * * @readonly */ get id(): string { return this.data.id; } /** * The number of members in the project. * * @readonly */ get memberCount(): number { return this.data.memberCount; } /** * The number of models in the project. * * @readonly */ get modelCount(): number { return this.data.modelCount; } /** * Project name. */ get name(): string { return this.data.name; } set name(value: string) { this.data.name = value; } /** * Project owner information. * * @property {string} userId - User ID. * @property {string} userName - User name. * @property {string} name - First name. * @property {string} lastName - Last name. * @property {string} fullName - Full name. * @property {string} initials - Initials. * @property {string} email - User email. * @property {string} avatarUrl - User avatar image URL. * @readonly */ get owner(): IShortUserDescription { return this.data.owner; } /** * Project preview image URL. Use [setPreview()]{@link Project#setPreview} to change preview image. * * @readonly */ get previewUrl(): string { return this._data.previewUrl; } /** * `true` if project is public (shared) project. */ get public(): boolean { return this.data.public; } set public(value: boolean) { this.data.public = value; } /** * Project start date in the format specified in ISO 8601. */ get startDate(): string { return this.data.startDate; } set startDate(value: string | Date) { this.data.startDate = value instanceof Date ? value.toISOString() : value; } /** * The number of topics in the project. * * @readonly */ get topicCount(): number { return this.data.topicCount; } /** * Project last update time (UTC) in the format specified in ISO 8601. * * @readonly */ get updatedAt(): string { return this.data.updatedAt; } /** * Refresh project data. * * @async */ async checkout(): Promise { this.data = await json(this.internalGet("")); return this; } /** * Update project data on the server. * * @async * @param data - Raw project data. */ async update(data: any): Promise { this.data = await json(this.internalPut("", data)); return this; } /** * Delete the project from the server. * * @async * @returns Returns the raw data of a deleted project. */ delete(): Promise { return text(this.internalDelete("")).then((text) => { // TODO fix for server 23.5 and below try { return JSON.parse(text); } catch { return { id: this.id }; } }); } /** * Save project data changes to the server. Call this method to update project data on the * server after any changes. * * @async */ save(): Promise { return this.update(this.data); } /** * Set or remove the project preview. * * @async * @param image - Preview image. Can be a Data URL string, ArrayBuffer, Blob or * Web API File object. Setting the `image` to `null` will remove the preview. */ async setPreview(image?: ArrayBuffer | Blob | globalThis.File | FormData | string | null) { if (image) { this.data = await json(this.internalPost("/preview", image)); } else { this.data = await json(this.internalDelete("/preview")); } return this; } /** * Returns a list of project roles. Project {@link Member | members} have different abilities * depending on the role they have in a project. * * @async */ getRoles(): Promise { return json(this.internalGet("/roles")).then((array) => array.map((data) => new Role(data, this.id, this.httpClient)) ); } /** * Returns the role information. * * @async * @param name - Role name. */ getRole(name: string): Promise { return json(this.internalGet(`/roles/${name}`)).then((data) => new Role(data, this.id, this.httpClient)); } /** * Create a new project role. * * @async * @param name - Role name. * @param description - Role description. * @param permissions - Actions are allowed to be performed. See * {@link Role#permissions | Role.permissions} for more details. */ createRole(name: string, description: string, permissions: any): Promise { return json( this.internalPost("/roles", { name, description, permissions: permissions || {}, }) ).then((data) => new Role(data, this.id, this.httpClient)); } /** * Delete project role. * * @async * @param name - Role name. * @returns Returns the raw data of a deleted role. */ deleteRole(name: string): Promise { return json(this.internalDelete(`/roles/${name}`)); } /** * Returns a list of project members. * * @async */ getMembers(): Promise { return json(this.internalGet("/members")).then((array) => array.map((data) => new Member(data, this.id, this.httpClient)) ); } /** * Returns the member information. * * @async * @param memberId - Member ID. */ getMember(memberId: string): Promise { return json(this.internalGet(`/members/${memberId}`)).then((data) => new Member(data, this.id, this.httpClient)); } /** * Add a user to the project to become a member and have permission to perform actions. * * @async * @param userId - User ID. * @param role - User role from the list of project roles. */ addMember(userId: string, role: string): Promise { return json(this.internalPost("/members", { userId, role })).then( (data) => new Member(data, this.id, this.httpClient) ); } /** * Remove a member from a project. * * @async * @param memberId - Member ID. * @returns Returns the raw data of a deleted member. */ removeMember(memberId: string): Promise { return json(this.internalDelete(`/members/${memberId}`)); } /** * Information about the file (model) that can be reference in the project topics. * * @typedef {any} FileInformation * @property {any[]} display_information - The list of fields to allow users to associate the * file with a server model. * @property {string} display_information.field_display_name - Field display name. * @property {string} display_information.field_value - Field value. * @property {any} file - The file reference object. * @property {string} file.file_name - File name. * @property {string} file.reference - File ID. */ /** * Returns a list of project files. To add a file to this list, create a `project` permission * on the file using {@link File#createPermission | File.createPermission()}. * * @async */ getFilesInformation(): Promise { return json(this.httpClient.get(`/bcf/3.0/projects/${this.data.id}/files_information`)).then((items) => { items.forEach((item) => { const getFieldValue = (displayName: string) => { return (item.display_information.find((x) => x.field_display_name === displayName) || {}).field_value; }; const previewUrl = `${this.httpClient.serverUrl}/files/${item.file.reference}/preview`; const ownerAvatarUrl = `${this.httpClient.serverUrl}/users/${getFieldValue("Owner")}/avatar`; const ownerFirstName = getFieldValue("Owner First Name"); const ownerLastName = getFieldValue("Owner Last Name"); const ownerUserName = getFieldValue("Owner User Name"); const ownerFullName = userFullName(ownerFirstName, ownerLastName, ownerUserName); const ownerInitials = userInitials(ownerFullName); item.display_information.push({ field_display_name: "Preview URL", field_value: previewUrl }); item.display_information.push({ field_display_name: "Owner Avatar URL", field_value: ownerAvatarUrl }); item.display_information.push({ field_display_name: "Owner Full Name", field_value: ownerFullName }); item.display_information.push({ field_display_name: "Owner Initials", field_value: ownerInitials }); // updatedBy since 24.10 const updatedByAvatarUrl = `${this.httpClient.serverUrl}/users/${getFieldValue("Updated By")}/avatar`; const updatedByFirstName = getFieldValue("Updated By First Name"); const updatedByLastName = getFieldValue("Updated By Last Name"); const updatedByUserName = getFieldValue("Updated By User Name"); const updatedByFullName = userFullName(updatedByFirstName, updatedByLastName, updatedByUserName); const updatedByInitials = userInitials(updatedByFullName); item.display_information.push({ field_display_name: "Updated By Avatar URL", field_value: updatedByAvatarUrl }); item.display_information.push({ field_display_name: "Updated By Full Name", field_value: updatedByFullName }); item.display_information.push({ field_display_name: "Updated By Initials", field_value: updatedByInitials }); // geometryType since 24.12 const geometry = getFieldValue("Geometry Status"); const geometryGltf = getFieldValue("GeometryGltf Status"); const geometryType = geometry === "done" ? "vsfx" : geometryGltf === "done" ? "gltf" : ""; item.display_information.push({ field_display_name: "Geometry Type", field_value: geometryType }); }); return items; }); } }