///////////////////////////////////////////////////////////////////////////////
// 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;
});
}
}