diff --git a/twake/backend/node/config/default.json b/twake/backend/node/config/default.json index 9a0c6491..06dc5c2b 100644 --- a/twake/backend/node/config/default.json +++ b/twake/backend/node/config/default.json @@ -8,37 +8,37 @@ "mobile_appstore": "https://apps.apple.com/fr/app/twake/id1588764852?l=en", "mobile_googleplay": "https://play.google.com/store/apps/details?id=com.twake.twake&gl=FR" }, + "app_grid": [ + { + "name": "Tmail", + "logo": "/public/img/grid/tmail.png", + "url": "https://tmail.linagora.com/" + }, + { + "name": "Twake", + "logo": "/public/img/grid/twake.png", + "url": "https://twake.app/" + } + ], "accounts": { - "_type": "console", + "_type": "remote", "type": "internal", "internal": { "disable_account_creation": false }, - "console": { - "account_management_url": "http://web.twake-console.local/profile?company-code={company_id}", + "remote": { "authority": "http://auth.example.com/", "client_id": "twakeweb", + "client_secret": "", + "issuer": "", + "audience": "", + "redirect_uris": [""], + "account_management_url": "http://web.twake-console.local/profile?company-code={company_id}", "collaborators_management_url": "http://web.twake-console.local/compaies/{company_id}/users?company-code={company_id}", "company_management_url": "http://web.twake-console.local/companies?company-code={company_id}" } } }, - "console": { - "_type": "remote", - "type": "internal", - "remote": { - "new_console": false, - "username": "", - "password": "12345678", - "url": "https://some-remote-console-url/", - "hook": { - "token": "12345678" - } - }, - "internal": { - "disable_account_creation": false - } - }, "sentry": { "dsn": "" }, @@ -63,10 +63,6 @@ "root": "./public" } }, - "phpnode": { - "secret": "api_supersecret", - "php_endpoint": "http://nginx:80" - }, "websocket": { "path": "/socket/", "adapters": { @@ -154,7 +150,6 @@ "api_key": "secret", "sender": "noreply@twake.app" }, - "applications": [], "services": [ "auth", "push", diff --git a/twake/backend/node/package.json b/twake/backend/node/package.json index 5caaa195..19ee7ab8 100644 --- a/twake/backend/node/package.json +++ b/twake/backend/node/package.json @@ -120,6 +120,7 @@ "@fastify/formbody": "^6.0.0", "@fastify/static": "^5.0.1", "@ffprobe-installer/ffprobe": "^1.4.1", + "@okta/jwt-verifier": "^3.0.1", "@sentry/node": "^6.19.7", "@sentry/tracing": "^6.19.7", "@socket.io/redis-adapter": "^7.1.0", @@ -160,6 +161,7 @@ "get-website-favicon": "^0.0.7", "html-metadata-parser": "^2.0.4", "html-to-text": "^8.2.1", + "idtoken-verifier": "^2.2.3", "jsonwebtoken": "^8.5.1", "keyv": "^4.5.0", "lodash": "^4.17.21", @@ -172,6 +174,7 @@ "node-cron": "^3.0.0", "node-fetch": "^2.6.7", "node-uuid": "^1.4.8", + "openid-client": "^5.4.0", "ora": "^5.4.0", "pdf-parse": "^1.1.1", "pdf2pic": "^2.1.4", diff --git a/twake/backend/node/src/cli/cmds/console.ts b/twake/backend/node/src/cli/cmds/console.ts deleted file mode 100644 index a1d1eee4..00000000 --- a/twake/backend/node/src/cli/cmds/console.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CommandModule } from "yargs"; - -const command: CommandModule = { - describe: "Manage Twake Console", - command: "console ", - builder: yargs => - yargs.commandDir("console_cmds", { - visit: commandModule => commandModule.default, - }), - // eslint-disable-next-line @typescript-eslint/no-empty-function - handler: () => {}, -}; - -export default command; diff --git a/twake/backend/node/src/cli/cmds/console_cmds/merge.ts b/twake/backend/node/src/cli/cmds/console_cmds/merge.ts deleted file mode 100644 index c6e093e5..00000000 --- a/twake/backend/node/src/cli/cmds/console_cmds/merge.ts +++ /dev/null @@ -1,257 +0,0 @@ -import yargs from "yargs"; -import ora from "ora"; -import twake from "../../../twake"; -import { CompanyReport, UserReport } from "../../../services/console/types"; -import Company from "../../../services/user/entities/company"; -import gr from "../../../services/global-resolver"; -import { ConsoleServiceImpl } from "../../../services/console/service"; - -/** - * Merge command parameters. Check the builder definition below for more details. - */ -type MergeParams = { - url: string; - concurrent: number; - dry: boolean; - console: string; - link: boolean; - csv: boolean; - client: string; - secret: string; -}; - -const services = [ - "platform-services", - "user", - "channels", - "notifications", - "database", - "webserver", - "message-queue", - "console", -]; - -const command: yargs.CommandModule = { - command: "merge", - describe: "Merge Twake Chat users in the Twake Console", - builder: { - url: { - default: "http://localhost:8080", - type: "string", - description: "URL of the Twake console", - }, - concurrent: { - default: 1, - type: "number", - description: "Number of concurrent imports", - }, - dry: { - default: false, - type: "boolean", - description: "Make a dry run without creating anything on the Twake console", - }, - csv: { - default: false, - type: "boolean", - description: "Generate result as CSV", - }, - console: { - default: "console", - type: "string", - description: "The console service identifier to user to link user and companies", - }, - link: { - default: true, - type: "boolean", - description: - "Link the companies/users to external companies/user. Works with the --console parameter", - implies: "console", - }, - client: { - default: "twake", - type: "string", - description: "Client identifier to be used to authenticate calls to the Console", - }, - secret: { - default: "secret", - type: "string", - description: "Client secret to be used to authenticate calls to the Console", - }, - }, - handler: async argv => { - const spinner = ora({ text: `Importing Twake data on ${argv.url}` }).start(); - const platform = await twake.run(services); - await gr.doInit(platform); - const consoleService = platform.getProvider("console"); - const merge = consoleService.merge( - argv.url, - argv.concurrent, - argv.dry, - argv.console, - argv.link, - argv.client, - argv.secret, - ); - const start = Date.now(); - let stop: number = start; - const userReports: UserReport[] = []; - const companyReports: CompanyReport[] = []; - const ownerReports: UserReport[] = []; - let companiesWithoutAdmin: Company[] = []; - - const process = merge.subscribe({ - next: report => { - if (report.type === "company:created") { - const companyReport = report.data as CompanyReport; - companyReports.push(companyReport); - - if (companyReport.status === "success") { - spinner.succeed( - `Company created: ${companyReport.company.source.id} ${companyReport.company.source.displayName}`, - ); - } else { - spinner.fail( - `Creation error for company ${companyReport.company.source.id} (${companyReport.company.source.displayName}): ${companyReport?.error?.message}`, - ); - } - } - - if (report.type === "user:created") { - const userReport = report.data as UserReport; - userReports.push(userReport); - - if (userReport.status === "success") { - spinner.succeed( - `Company ${userReport.user.source.company.id}: User created ${userReport.user.destination.id}`, - ); - } else { - spinner.fail( - `Company ${userReport.user.source.company.id}: User creation error ${userReport.user.destination.id}: ${userReport?.error?.message}`, - ); - } - } - - if (report.type === "user:updated") { - const userReport = report.data as UserReport; - ownerReports.push(userReport); - if (userReport.status === "success") { - spinner.succeed( - `Company ${userReport.destinationCompanyCode} owner updated to user ${userReport.destinationId}`, - ); - } else { - spinner.fail( - `Company ${userReport.destinationCompanyCode}: Owner update error for user ${userReport.destinationId}: ${userReport?.error?.message}`, - ); - } - } - - if (report.type === "processing:owner") { - spinner.start("Computing company owners, please wait..."); - } - - if (report.type === "user:updating") { - //spinner.succeed(`Updating owner for company ${report?.company?.sourceId}`); - } - - if (report.type === "log") { - report.message && (spinner.text = report.message); - } - - if (report.type === "company:withoutadmin") { - report.message && (spinner.text = report.message); - const companies = (report.data || []) as Company[]; - - if (companies.length) { - companiesWithoutAdmin = companies; - } - } - }, - error: () => { - spinner.fail("Fatal error"); - }, - complete: async () => { - stop = Date.now(); - spinner.succeed("Merge is complete"); - displayStats(); - await tearDown(); - }, - }); - - async function tearDown() { - await platform.stop(); - process.unsubscribe(); - } - - function reportAsCSV() { - const users = userReports.map( - user => - `${user.sourceId},${user.destinationId},${user.destinationCompanyCode},${user.status}`, - ); - const userCSV = [...["sourceId,destinationId,destinationCompanyCode,status"], ...users].join( - "\n", - ); - - const companies = companyReports.map( - company => `${company.sourceId},${company.destinationCode},${company.status}`, - ); - const companyCSV = [...["sourceId,destinationCode,status"], ...companies].join("\n"); - - console.log(userCSV); - console.log(companyCSV); - } - - function displayStats() { - const userFailures = userReports.filter(user => user.error); - const companyFailures = companyReports.filter(company => company.error); - const ownerFailures = ownerReports.filter(report => report.error); - - console.log("# Import report"); - console.log(`Data imported in ${(stop - start) / 1000} seconds`); - console.log("## Company"); - console.log("- Companies success:", companyReports.filter(company => !company.error).length); - console.log("- Companies failure:", companyFailures.length); - if (companyFailures.length) { - console.log("### Failures"); - companyFailures.forEach(failure => - console.log( - `- Company ${failure.company.source.id} error: ${failure.company.error?.message}`, - ), - ); - } - - console.log("## User"); - console.log("- Users success:", userReports.filter(user => !user.error).length); - console.log("- Users failure:", userFailures.length); - if (userFailures.length) { - console.log("### Failures"); - userFailures.forEach(failure => - console.log( - `- User ${failure.user.source.user.user_id} error: ${failure.user.error?.message}`, - ), - ); - } - - console.log("## Owner"); - console.log("- Owners success:", ownerReports.filter(report => !report.error).length); - console.log("- Owners failure:", ownerFailures.length); - if (ownerFailures.length) { - console.log("### Failures"); - ownerFailures.forEach(report => - console.log(`- User ${report.destinationId} error: ${report?.error?.message}`), - ); - } - - console.log("## Warnings"); - console.log("### Companies witout admins"); - companiesWithoutAdmin.forEach(company => { - console.log(`- ${company.id} - ${company.displayName}`); - }); - - if (argv.csv) { - reportAsCSV(); - } - } - }, -}; - -export default command; diff --git a/twake/backend/node/src/services/console/client-interface.ts b/twake/backend/node/src/services/console/client-interface.ts index 878b90ce..2663d065 100644 --- a/twake/backend/node/src/services/console/client-interface.ts +++ b/twake/backend/node/src/services/console/client-interface.ts @@ -60,7 +60,7 @@ export interface ConsoleServiceClient { fetchCompanyInfo(consoleCompanyCode: string): Promise; - getUserByAccessToken(accessToken: string): Promise; + getUserByAccessToken(idToken: string): Promise; resendVerificationEmail(email: string): Promise; } diff --git a/twake/backend/node/src/services/console/clients/internal.ts b/twake/backend/node/src/services/console/clients/internal.ts index 59af4f0b..13caf3b9 100644 --- a/twake/backend/node/src/services/console/clients/internal.ts +++ b/twake/backend/node/src/services/console/clients/internal.ts @@ -95,7 +95,7 @@ export class ConsoleInternalClient implements ConsoleServiceClient { throw new Error("Method should not be implemented."); } - getUserByAccessToken(_accessToken: string): Promise { + getUserByAccessToken(_idToken: string): Promise { logger.info("Internal: getUserByAccessToken"); throw new Error("Method should not be implemented."); } diff --git a/twake/backend/node/src/services/console/clients/remote.ts b/twake/backend/node/src/services/console/clients/remote.ts index 14e8ab06..03749b67 100644 --- a/twake/backend/node/src/services/console/clients/remote.ts +++ b/twake/backend/node/src/services/console/clients/remote.ts @@ -1,5 +1,5 @@ -import axios, { AxiosInstance } from "axios"; -import { v1 as uuidv1 } from "uuid"; +import { AxiosInstance } from "axios"; +import { Issuer } from "openid-client"; import { ConsoleServiceClient } from "../client-interface"; import { ConsoleCompany, @@ -14,16 +14,12 @@ import { UpdatedConsoleUserRole, } from "../types"; -import User, { getInstance } from "../../user/entities/user"; -import Company, { - CompanySearchKey, - getInstance as getCompanyInstance, -} from "../../user/entities/company"; +import OktaJwtVerifier from "@okta/jwt-verifier"; import { CrudException } from "../../../core/platform/framework/api/crud-service"; -import coalesce from "../../../utils/coalesce"; import { logger } from "../../../core/platform/framework/logger"; -import { CompanyFeaturesEnum, CompanyLimitsEnum } from "../../user/web/types"; import gr from "../../global-resolver"; +import Company, { CompanySearchKey } from "../../user/entities/company"; +import User from "../../user/entities/user"; import { ConsoleServiceImpl } from "../service"; export class ConsoleRemoteClient implements ConsoleServiceClient { @@ -31,92 +27,30 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { client: AxiosInstance; private infos: ConsoleOptions; + private verifier: OktaJwtVerifier; constructor(consoleInstance: ConsoleServiceImpl, private dryRun: boolean) { this.infos = consoleInstance.consoleOptions; - this.client = axios.create({ baseURL: this.infos.url }); + this.verifier = new OktaJwtVerifier({ + issuer: this.infos.issuer, + }); + } + fetchCompanyInfo(consoleCompanyCode: string): Promise { + throw new Error("Method not implemented."); + } + resendVerificationEmail(email: string): Promise { + throw new Error("Method not implemented."); } private auth() { - return { username: this.infos.username, password: this.infos.password }; + return {}; } async addUserToCompany( company: ConsoleCompany, user: CreateConsoleUser, ): Promise { - logger.info("Remote: addUserToCompany"); - - const isNewConsole = this.infos.new_console; - - if (this.dryRun) { - return { - _id: uuidv1(), - }; - } - - if (user.skipInvite && user.name && user.email && user.password) { - return this.client - .post(`/api/companies/${company.code}/users`, user, { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - params: { - skipInvite: user.skipInvite, - }, - }) - .then(({ data }) => data); - } else { - const invitationData = { - role: user.role, - emails: [ - { - email: user.email, - role: user.role, - }, - ], - inviter: { email: user.inviterEmail }, - ...(isNewConsole - ? { - applicationCodes: ["twake"], - } - : {}), - }; - - const result = await this.client - .post( - `/api/companies/${company.code}/${isNewConsole ? "invitations" : "users/invitation"}`, - invitationData, - { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }, - ) - .then(async ({ data, status }) => { - //Fixme: When console solve https://gitlab.com/COMPANY_LINAGORA/software/saas/twake-console-account/-/issues/36 - // we can remove this fallback - if ([200, 201].indexOf(status) >= 0) { - return data; - } else { - return this.client - .post(`/api/companies/${company.code}/users`, user, { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - params: { - skipInvite: user.skipInvite, - }, - }) - .then(({ data }) => data); - } - }); - - return result; - } + return null; } async updateUserRole( @@ -124,47 +58,12 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { user: UpdateConsoleUserRole, ): Promise { logger.info("Remote: updateUserRole"); - - if (this.dryRun) { - return { - id: user.id, - role: user.role, - }; - } - - const result = await this.client - .put( - `/api/companies/${company.code}/users/${user.id}`, - { role: user.role }, - { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }, - ) - .then(({ data }) => data); - - return result; + return null; } async createCompany(company: CreateConsoleCompany): Promise { logger.info("Remote: createCompany"); - - if (this.dryRun) { - return company; - } - - const result = await this.client - .post("/api/companies", company, { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }) - .then(({ data }) => data); - - return result; + return null; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -175,216 +74,11 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { } async updateLocalCompanyFromConsole(partialCompanyDTO: ConsoleHookCompany): Promise { - logger.info("Remote: updateLocalCompanyFromConsole"); - - const companyDTO = await this.fetchCompanyInfo(partialCompanyDTO.details.code); - - let company = await gr.services.companies.getCompany({ - identity_provider_id: companyDTO.details.code, - }); - - if (!company) { - const newCompany = getCompanyInstance({ - id: uuidv1(), - identity_provider: "console", - identity_provider_id: companyDTO.details.code, - }); - company = await gr.services.companies.createCompany(newCompany); - } - - const details = companyDTO.details; - - if (details) { - company.name = coalesce(details.name, company.name); - company.displayName = coalesce(details.name, company.displayName); - - const avatar = details.avatar; - - company.logo = - details.logo || - (avatar && avatar.type && avatar.type !== "url" - ? this.infos.url.replace(/\/$/, "") + "/api/avatars/" + avatar.value - : companyDTO.value || ""); - } - - if (!company.plan) { - company.plan = { name: "", limits: undefined, features: undefined }; - } - - const limits = companyDTO.limits.twake || companyDTO.limits; - - //FIXME this is a hack right now! - let planFeatures: any = { - [CompanyFeaturesEnum.CHAT_GUESTS]: true, - [CompanyFeaturesEnum.CHAT_MESSAGE_HISTORY]: true, - [CompanyFeaturesEnum.CHAT_MULTIPLE_WORKSPACES]: true, - [CompanyFeaturesEnum.CHAT_EDIT_FILES]: true, - [CompanyFeaturesEnum.CHAT_UNLIMITED_STORAGE]: true, - [CompanyFeaturesEnum.COMPANY_INVITE_MEMBER]: true, - }; - - if (limits.members < 0 && this.infos.type === "remote") { - //Hack to say this is free version - planFeatures = { - [CompanyFeaturesEnum.CHAT_GUESTS]: false, - [CompanyFeaturesEnum.CHAT_MESSAGE_HISTORY]: false, - [CompanyFeaturesEnum.CHAT_MULTIPLE_WORKSPACES]: false, - [CompanyFeaturesEnum.CHAT_EDIT_FILES]: false, - [CompanyFeaturesEnum.CHAT_UNLIMITED_STORAGE]: false, // Currently inactive - }; - company.plan.name = "free"; - } else { - company.plan.name = "standard"; - } - company.plan.features = { ...planFeatures }; - company.plan.limits = { - [CompanyLimitsEnum.CHAT_MESSAGE_HISTORY_LIMIT]: 10000, // To remove duplicata since we define this in formatCompany function - [CompanyLimitsEnum.COMPANY_MEMBERS_LIMIT]: limits["members"], - [CompanyLimitsEnum.COMPANY_GUESTS_LIMIT]: limits["guests"], - }; - - company.stats = coalesce(companyDTO.stats, company.stats); - - await gr.services.companies.updateCompany(company); - - return company; + return null; } async updateLocalUserFromConsole(code: string): Promise { - logger.info("Remote: updateLocalUserFromConsole"); - - const userDTO = await this.fetchUserInfo(code); - - if (!userDTO) { - throw CrudException.badRequest("User not found on Console"); - } - - const roles = userDTO.roles.filter( - role => role.applications === undefined || role.applications.find(a => a.code === "twake"), - ); - - let user = await gr.services.users.getByConsoleId(userDTO._id); - - if (!user) { - if (!userDTO.email) { - throw CrudException.badRequest("Email is required"); - } - - let username = userDTO.email - .split("@")[0] - .toLocaleLowerCase() - .replace(/[^a-zA-Z0-9]/g, "") - .replace(/ +/g, "_"); - - if (await gr.services.users.isEmailAlreadyInUse(userDTO.email)) { - const user = await gr.services.users.getByEmail(userDTO.email); - - if (user.identity_provider === "console") { - let emailUserOnConsole = null; - try { - emailUserOnConsole = await this.fetchUserInfo(user.identity_provider_id); - } catch (err) {} - if (emailUserOnConsole?._id) { - throw CrudException.badRequest( - `Console user not created because email already exists on console with different id: ${emailUserOnConsole._id} while requested to provision user with id: ${userDTO._id}`, - ); - } - - //If not present on console, then anonymise this one and create a new one - await gr.services.users.anonymizeAndDelete( - { id: user.id }, - { - user: { id: user.id, server_request: true }, - }, - ); - - //Now we can create the new user - } - } - - username = await gr.services.users.getAvailableUsername(username); - if (!username) { - throw CrudException.badRequest("Console user not created because username already exists"); - } - - user = getInstance({}); - user.username_canonical = (username || "").toLocaleLowerCase(); - user.email_canonical = userDTO.email; - user.deleted = false; - } - - user.email_canonical = coalesce(userDTO.email, user.email_canonical); - user.phone = ""; - user.first_name = coalesce(userDTO.name, user.first_name); - user.last_name = coalesce(userDTO.surname, user.last_name); - user.identity_provider = "console"; - user.identity_provider_id = userDTO._id; - user.mail_verified = coalesce(userDTO.isVerified, user.mail_verified); - if (userDTO.preference) { - user.preferences = user.preferences || {}; - user.preferences.allow_tracking = coalesce( - userDTO.preference.allowTrackingPersonalInfo, - user.preferences?.allow_tracking, - ); - user.preferences.language = coalesce(userDTO.preference.locale, user.preferences?.language); - user.preferences.timezone = coalesce(userDTO.preference.timeZone, user.preferences?.timezone); - } - - const avatar = userDTO.avatar; - - user.picture = - avatar && avatar.type && avatar.type !== "url" - ? this.infos.url.replace(/\/$/, "") + "/api/avatars/" + avatar.value - : ""; - - await gr.services.users.save(user); - - const getCompanyByCode = async (companyCode: string) => { - let company = await gr.services.companies.getCompany({ - identity_provider_id: companyCode, - }); - if (!company) { - const companyDTO = await this.fetchCompanyInfo(companyCode); - await this.updateLocalCompanyFromConsole(companyDTO); - company = await gr.services.companies.getCompany({ - identity_provider_id: companyCode, - }); - } - return company; - }; - - const updatedListOfCompanies = []; - if (roles) { - for (const role of roles) { - const companyConsoleCode = role.targetCode; - const roleName = role.roleCode; - const company = await getCompanyByCode(companyConsoleCode); - if (!company) { - throw CrudException.notFound(`Company ${companyConsoleCode} not found`); - } - //Make sure user is active, if not we remove it - if (role.status !== "deactivated") { - updatedListOfCompanies.push(company); - const applications = (role.applications || []).map(a => a.code); - await gr.services.companies.setUserRole(company.id, user.id, roleName, applications); - } - } - } - - // Remove user from companies not in the console - const currentCompanies = await gr.services.companies.getAllForUser(user.id); - for (const company of currentCompanies) { - if (!updatedListOfCompanies.map(c => c.id).includes(company.group_id)) { - await gr.services.companies.removeUserFromCompany( - { id: company.group_id }, - { id: user.id }, - ); - } - } - - await gr.services.users.save(user, { user: { id: user.id, server_request: true } }); - - return user; + return null; } async removeCompanyUser(consoleUserId: string, company: Company): Promise { @@ -419,81 +113,25 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { await gr.services.companies.removeCompany(companySearchKey); } - fetchCompanyInfo(consoleCompanyCode: string): Promise { - logger.info(`Remote: fetchCompanyInfo ${consoleCompanyCode}`); - return this.client - .get(`/api/companies/${consoleCompanyCode}`, { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }) - .then(({ data }) => data.company) - .catch(e => { - if (e.response.status === 401) { - throw CrudException.forbidden("Bad console credentials"); - } - throw e; - }); - } - - fetchUserInfo(consoleUserId: string): Promise { - logger.info(`Remote: fetchUserInfo ${consoleUserId}`); - return this.client - .get(`/api/users/${consoleUserId}`, { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }) - .then(({ data }) => data) - .catch(e => { - if (e.response.status === 401) { - throw CrudException.forbidden("Bad console credentials"); - } - throw e; - }); - } - - getUserByAccessToken(accessToken: string): Promise { - logger.info("Remote: getUserByAccessToken"); - return this.client - .get("/api/users/profile", { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, - }, - }) - .then(({ data }) => data) - .catch(e => { - if (e.response?.status === 401) { - throw CrudException.forbidden("Bad access token credentials"); - } - throw e; - }); - } - - async resendVerificationEmail(email: string) { - logger.info("Remote: resendVerificationEmail"); - return this.client - .post( - "/api/users/resend-verification-email", - { - email, - }, - { - auth: this.auth(), - headers: { - "Content-Type": "application/json", - }, - }, - ) - .then(({ data }) => data) - .catch(e => { - if (e.response.status === 401) { - throw CrudException.forbidden("Bad credentials"); - } - throw e; - }); + async getUserByAccessToken(idToken: string): Promise { + const user = (await this.verifier.verifyIdToken(idToken, this.infos.audience)).claims as any; + console.log("user", user); + return { + _id: user.sub, + roles: [] as any, + email: user.email, + name: user.given_name, + surname: user.family_name, + isVerified: true, + preference: { + locale: user.locale, + timeZone: 0, + allowTrackingPersonalInfo: true, + }, + avatar: { + type: "url", + value: user.picture, + }, + }; } } diff --git a/twake/backend/node/src/services/console/processing/merge.ts b/twake/backend/node/src/services/console/processing/merge.ts deleted file mode 100644 index 62fe2316..00000000 --- a/twake/backend/node/src/services/console/processing/merge.ts +++ /dev/null @@ -1,426 +0,0 @@ -import passwordGenerator from "generate-password"; -import { concat, EMPTY, forkJoin, from, Observable, ReplaySubject } from "rxjs"; -import { - count, - distinct, - filter, - groupBy, - map, - mergeMap, - min, - share, - tap, - toArray, -} from "rxjs/operators"; -import { getLogger } from "../../../core/platform/framework"; -import { - ExecutionContext, - Paginable, - Pagination, -} from "../../../core/platform/framework/api/crud-service"; -import Company from "../../user/entities/company"; -import User from "../../user/entities/user"; -import { - CompanyCreatedStreamObject, - CompanyReport, - ConsoleOptions, - CreatedConsoleCompany, - CreatedConsoleUser, - MergeProgress, - ProcessReport, - UpdateConsoleUserRole, - UserCreatedStreamObject, - UserReport, -} from "../types"; -import { getInstance as getExternalUserInstance } from "../../user/entities/external_user"; -import { getInstance as getExternalGroupInstance } from "../../user/entities/external_company"; -import CompanyUser from "../../user/entities/company_user"; -import { ConsoleRemoteClient } from "../clients/remote"; -import { ConsoleServiceClient } from "../client-interface"; -import { DatabaseServiceAPI } from "../../../core/platform/services/database/api"; -import { CompanyUserRole } from "../../user/web/types"; -import gr from "../../global-resolver"; -import { ConsoleServiceImpl } from "../service"; - -const logger = getLogger("console.process.merge"); - -export class MergeProcess { - private client: ConsoleServiceClient; - - constructor( - private database: DatabaseServiceAPI, - private dryRun: boolean, - private consoleId: string = "console", - private linkExternal: boolean = true, - consoleClientOptions: ConsoleOptions, - ) { - const consoleService = new ConsoleServiceImpl(consoleClientOptions); - this.client = new ConsoleRemoteClient(consoleService, dryRun); - } - - merge(concurrent: number = 1): MergeProgress { - const progress$ = new ReplaySubject(); - const { users$, companies$ } = this.getStreams(concurrent); - - const owners$ = users$.pipe( - // get only the admins - filter(userInCompany => userInCompany.source.user.level.valueOf() === 3), - // group by company id - groupBy(userInCompany => userInCompany.source.company.id), - mergeMap(group => - group.pipe( - // get the admin with the smaller creation date, let's say that this is the first one in the company so it is the owner - min((a, b) => { - return (a.source?.user?.dateAdded || 0) <= (b.source?.user?.dateAdded || 0) ? -1 : 1; - }), - ), - ), - ); - - const numberOfAdmins$ = users$.pipe(count(user => user.source.user.level.valueOf() === 3)); - const companyWithoutAdmin$ = users$.pipe( - // group by company - groupBy(user => user.source.company.id), - mergeMap(group => - group.pipe( - // get users as array - // TODO: we may be able to do this without doing a toArray but just by playing with the group - toArray(), - // filter groups where there is no user with level === 3 - filter(users => !users.some(user => user.source.user.level.valueOf() === 3)), - // take first element to get the company - map(users => users[0].source.company), - ), - ), - toArray(), - ); - - const companiesSubscription = companies$.subscribe({ - next(company) { - progress$.next({ - type: "company:created", - data: { - sourceId: company.source.id, - destinationCode: company.destination.code, - status: company.error ? "failure" : "success", - company, - error: company.error, - } as CompanyReport, - }); - }, - }); - - const usersSubscription = users$.subscribe({ - next(user) { - progress$.next({ - type: "user:created", - data: { - sourceId: user.source.user.id, - destinationId: user.destination.id, - destinationCompanyCode: user.destination.companyCode, - status: user.error ? "failure" : "success", - user, - error: user.error, - } as UserReport, - }); - }, - error(err: Error) { - console.error(err); - }, - }); - - const updateOwners$ = owners$.pipe( - mergeMap(owner => this.updateUserRole(owner, "owner"), concurrent), - tap(user => - progress$.next({ - type: "user:updated", - data: { - sourceId: user.source.source.user.id, - destinationId: user.source.destination.id, - destinationCompanyCode: user.source.destination.companyCode, - status: user.result.error ? "failure" : "success", - user: user.source, - error: user.result.error, - } as UserReport, - }), - ), - ); - - const process$ = forkJoin([users$, companies$, numberOfAdmins$, companyWithoutAdmin$]).pipe( - tap(result => { - progress$.next({ type: "log", message: `There are ${result[2]} admins` }); - progress$.next({ - type: "company:withoutadmin", - message: `Companies without admins ${result[3].length}`, - data: result[3], - }); - progress$.next({ type: "processing:owner" }); - }), - mergeMap(() => from(updateOwners$), concurrent), - ); - - const process = process$.subscribe({ - complete() { - usersSubscription.unsubscribe(); - companiesSubscription.unsubscribe(); - process.unsubscribe(); - progress$.complete(); - }, - }); - - return progress$; - } - - private getStreams( - concurrent: number = 1, - context?: ExecutionContext, - ): { - // hot companies observable - companies$: Observable; - // hot users observable - users$: Observable; - } { - const companies$ = this.getCompanies().pipe( - mergeMap(company => this.createCompany(company), concurrent), - // make it hot - share(), - ); - - const users$ = companies$.pipe( - filter(company => !company.error), - mergeMap(company => - this.getUserIds(company.source).pipe( - map(user => ({ - user, - company, - })), - ), - ), - mergeMap( - userInCompany => this.createUser(userInCompany.company.source, userInCompany.user, context), - concurrent, - ), - // make it hot - share(), - ); - - return { - companies$, - users$, - }; - } - - private getUserIds(company: Company, paginable?: Paginable): Observable { - const pagination = new Pagination( - paginable?.page_token, - paginable?.limitStr, - paginable?.reversed, - ); - - return from(gr.services.companies.getUsers({ group_id: company.id }, pagination)).pipe( - mergeMap(companyUsers => { - const items$ = from(companyUsers.getEntities()); - const next$ = companyUsers?.nextPage?.page_token - ? this.getUserIds(company, companyUsers.nextPage) - : EMPTY; - return concat(items$, next$); - }), - distinct(user => user.user_id), - ); - } - - private getCompanies(paginable?: Paginable): Observable { - return from(gr.services.companies.getCompanies(paginable)).pipe( - mergeMap(companiesResult => { - const items$ = from(companiesResult.getEntities()); - const next$ = companiesResult?.nextPage?.page_token - ? this.getCompanies(companiesResult.nextPage) - : EMPTY; - - return concat(items$, next$); - }), - distinct((company: Company) => company.id), - ); - } - - private async createCompany(company: Company): Promise { - logger.debug("Creating company in the Console %s", company.displayName); - let createdCompany: CreatedConsoleCompany; - let error; - - try { - createdCompany = await this.client.createCompany({ - code: company.id, - displayName: company.displayName, - avatar: { - type: "url", - value: company.logo, - }, - status: "active", - }); - - if (this.linkExternal) { - await this.createCompanyLink(company, createdCompany, this.consoleId); - } - } catch (err) { - logger.warn("Error while creating company %s", company.displayName); - error = err; - } - - return { - source: company, - destination: { - code: createdCompany?.code, - }, - error, - }; - } - - private async createUser( - company: Company, - companyUser: CompanyUser, - context: ExecutionContext, - ): Promise { - logger.debug("Creating user in console %o", companyUser.user_id); - let error: Error; - let result: CreatedConsoleUser; - - try { - const user = await gr.services.users.get({ id: companyUser.user_id }); - - if (!user) { - throw new Error(`User ${companyUser.user_id} not found`); - } - - const firstName = - user.first_name && user.first_name.trim().length ? user.first_name : user.email_canonical; - const lastName = - user.last_name && user.last_name.trim().length ? user.last_name : user.email_canonical; - const name = (firstName + " " + lastName).trim(); - - let role: "admin" | "guest" | "member" = - companyUser.level.valueOf() === 3 ? "admin" : "member"; - if (role != "admin") { - if (companyUser.isExterne) { - role = "guest"; - } - const workspacesUsers = await gr.services.workspaces.getAllForUser( - { userId: companyUser.user_id }, - { id: company.id }, - ); - workspacesUsers.forEach(e => { - if (e.isExternal) { - role = "guest"; - } - }); - } - - result = await this.client.addUserToCompany( - { code: company.id }, - { - email: user.email_canonical, - // console requires that firstname/lastname are defined and at least 1 chat long - firstName, - lastName, - name, - avatar: { - type: "url", - value: user.picture, - }, - password: passwordGenerator.generate({ - length: 10, - numbers: true, - }), - skipInvite: true, - role, - inviterEmail: "", - }, - ); - - const companyUserRepository = await this.database.getRepository( - "group_user", - CompanyUser, - ); - companyUser.role = role; - await companyUserRepository.save(companyUser, context); - - if (this.linkExternal) { - await this.createUserLink(user, result, this.consoleId); - } - } catch (err) { - logger.warn("Error while creating the user %o", companyUser.user_id); - error = err; - } - - return { - source: { user: companyUser, company }, - destination: { - id: result?._id, - companyCode: company.id, - }, - error, - }; - } - - private async createUserLink( - localUser: User, - remoteUser: CreatedConsoleUser, - serviceId: string, - ): Promise { - logger.debug("Creating user link for user %s", localUser.id); - if (this.dryRun) { - return; - } - - await gr.services.externalUser.createExternalUser( - getExternalUserInstance({ - service_id: serviceId, - external_id: remoteUser._id, - user_id: localUser.id, - }), - ); - } - - private async createCompanyLink( - localCompany: Company, - remoteCompany: CreatedConsoleCompany, - serviceId: string, - ): Promise { - if (this.dryRun) { - return; - } - - await gr.services.externalUser.createExternalGroup( - getExternalGroupInstance({ - service_id: serviceId, - company_id: localCompany.id, - external_id: remoteCompany.code, - }), - ); - } - - private updateUserRole( - user: UserCreatedStreamObject, - role: CompanyUserRole, - ): Promise<{ - source: UserCreatedStreamObject; - result: UpdateConsoleUserRole & { error?: Error }; - }> { - logger.debug( - `Updating user role for user ${user.source.user.id} in company ${user.source.company.id}`, - ); - return this.client - .updateUserRole({ code: user.destination.companyCode }, { id: user.destination.id, role }) - .then(result => ({ source: user, result })) - .catch((err: Error) => { - return { - source: user, - result: { - id: user.destination.id, - role, - error: err, - }, - }; - }); - } -} diff --git a/twake/backend/node/src/services/console/service.ts b/twake/backend/node/src/services/console/service.ts index 34e27fb1..0fa19e5d 100644 --- a/twake/backend/node/src/services/console/service.ts +++ b/twake/backend/node/src/services/console/service.ts @@ -1,5 +1,4 @@ import { DatabaseServiceAPI } from "../../core/platform/services/database/api"; -import { MergeProcess } from "./processing/merge"; import { ConsoleOptions, ConsoleType, MergeProgress } from "./types"; import { ConsoleServiceClient } from "./client-interface"; import { ConsoleClientFactory } from "./client-factory"; @@ -24,7 +23,7 @@ export class ConsoleServiceImpl implements TwakeServiceProvider { } async init(): Promise { - this.configuration = new Configuration("console"); + this.configuration = new Configuration("general.accounts"); assert(this.configuration, "console configuration is missing"); const type = this.configuration.get("type") as ConsoleType; assert(type, "console configuration type is not defined"); @@ -33,14 +32,12 @@ export class ConsoleServiceImpl implements TwakeServiceProvider { this.consoleOptions = { type: type, - new_console: s.new_console, - username: s.username, - password: s.password, - url: s.url, - hook: { - token: s.hook?.token, - public_key: s.hook?.public_key, - }, + authority: s.authority, + client_id: s.client_id, + client_secret: s.client_secret, + audience: s.audience, + issuer: s.issuer, + redirect_uris: s.redirect_uris, disable_account_creation: s.disable_account_creation, }; @@ -50,24 +47,6 @@ export class ConsoleServiceImpl implements TwakeServiceProvider { return this; } - merge( - baseUrl: string, - concurrent: number = 1, - dryRun: boolean = false, - console: string = "console", - link: boolean = true, - client: string, - secret: string, - _context?: ExecutionContext, - ): MergeProgress { - return new MergeProcess(this.services.database, dryRun, console, link, { - type: "remote", - username: client, - password: secret, - url: baseUrl, - } as ConsoleOptions).merge(concurrent); - } - getClient(): ConsoleServiceClient { return ConsoleClientFactory.create(this); } diff --git a/twake/backend/node/src/services/console/types.ts b/twake/backend/node/src/services/console/types.ts index 547f9e67..3b0dcf4a 100644 --- a/twake/backend/node/src/services/console/types.ts +++ b/twake/backend/node/src/services/console/types.ts @@ -126,14 +126,12 @@ export type ConsoleType = "remote" | "internal"; export type ConsoleOptions = { type: ConsoleType; - new_console: boolean; - username: string; - password: string; - url: string; - hook: { - token: string; - public_key?: string; - }; + authority: string; + client_id: string; + client_secret: string; + issuer: string; + audience: string; + redirect_uris: string[]; disable_account_creation: boolean; }; @@ -231,7 +229,7 @@ export interface ConsoleExecutionContext extends ExecutionContext { export interface AuthRequest { email?: string; password?: string; - remote_access_token?: string; + oidc_id_token?: string; } export interface AuthResponse { diff --git a/twake/backend/node/src/services/console/web/controller.ts b/twake/backend/node/src/services/console/web/controller.ts index d794dc34..524baa44 100644 --- a/twake/backend/node/src/services/console/web/controller.ts +++ b/twake/backend/node/src/services/console/web/controller.ts @@ -31,8 +31,8 @@ export class ConsoleController { } async auth(request: FastifyRequest<{ Body: AuthRequest }>): Promise { - if (request.body.remote_access_token) { - return { access_token: await this.authByToken(request.body.remote_access_token) }; + if (request.body.oidc_id_token) { + return { access_token: await this.authByToken(request.body.oidc_id_token) }; } else if (request.body.email && request.body.password) { return { access_token: await this.authByPassword(request.body.email, request.body.password) }; } else { @@ -283,12 +283,12 @@ export class ConsoleController { }); } - private async authByToken(accessToken: string): Promise { + private async authByToken(idToken: string): Promise { const client = gr.services.console.getClient(); - const userDTO = await client.getUserByAccessToken(accessToken); + const userDTO = await client.getUserByAccessToken(idToken); const user = await client.updateLocalUserFromConsole(userDTO._id); if (!user) { - throw CrudException.notFound(`User details not found for access token ${accessToken}`); + throw CrudException.notFound(`User details not found for access token ${idToken}`); } return gr.platformServices.auth.generateJWT(user.id, user.email_canonical, { track: user?.preferences?.allow_tracking || false, diff --git a/twake/backend/node/src/services/console/web/routes.ts b/twake/backend/node/src/services/console/web/routes.ts index a14a19a7..fee4cce3 100644 --- a/twake/backend/node/src/services/console/web/routes.ts +++ b/twake/backend/node/src/services/console/web/routes.ts @@ -14,27 +14,7 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) const accessControl = async ( request: FastifyRequest<{ Body: ConsoleHookBody; Querystring: ConsoleHookQueryString }>, ) => { - if (gr.services.console.consoleType != "remote") { - throw fastify.httpErrors.notImplemented("Hook service is only for the remote console"); - } - - if ( - (request.body.secret_key || request.query.secret_key) !== - gr.services.console.consoleOptions.hook.token - ) { - throw fastify.httpErrors.forbidden("Wrong secret"); - } - - const publicKey = gr.services.console.consoleOptions.hook.public_key; - - if (publicKey) { - const input = JSON.stringify({ content: request.body.content, type: request.body.type }); - const signatureBase64 = request.body.signature; - const verifier = crypto.createVerify("RSA-SHA512").update(input); - if (!verifier.verify(publicKey, signatureBase64, "base64")) { - throw fastify.httpErrors.forbidden("Signature verification failed"); - } - } + throw fastify.httpErrors.notImplemented("Hook service doesn't exist anymore"); }; fastify.route({ diff --git a/twake/backend/node/src/services/console/web/schemas.ts b/twake/backend/node/src/services/console/web/schemas.ts index 34db9196..6611b947 100644 --- a/twake/backend/node/src/services/console/web/schemas.ts +++ b/twake/backend/node/src/services/console/web/schemas.ts @@ -32,7 +32,7 @@ export const authenticationSchema = { properties: { email: { type: "string" }, password: { type: "string" }, - remote_access_token: { type: "string" }, + oidc_id_token: { type: "string" }, }, }, response: { diff --git a/twake/backend/node/src/services/general/types.ts b/twake/backend/node/src/services/general/types.ts index 67e96865..905a74fc 100644 --- a/twake/backend/node/src/services/general/types.ts +++ b/twake/backend/node/src/services/general/types.ts @@ -16,14 +16,15 @@ export type ServerConfiguration = { help_url: string | null; pricing_plan_url: string | null; app_download_url: string | null; + app_grid: { logo: string; name: string; url: string }[]; mobile: { mobile_redirect: string; mobile_appstore: string; mobile_googleplay: string; }; accounts: { - type: "console" | "internal"; - console: null | { + type: "remote" | "internal"; + remote: null | { authority: string; client_id: string; account_management_url: string; diff --git a/twake/backend/node/src/services/general/web/routes.ts b/twake/backend/node/src/services/general/web/routes.ts index 76fbd45b..6bb7d211 100644 --- a/twake/backend/node/src/services/general/web/routes.ts +++ b/twake/backend/node/src/services/general/web/routes.ts @@ -24,11 +24,12 @@ const routes: FastifyPluginCallback<{ configuration: ServerConfiguration["config "pricing_plan_url", "mobile", "app_download_url", + "app_grid", ), accounts: { type: accounts.type, - console: _.pick( - accounts.console, + remote: _.pick( + accounts.remote, "authority", "client_id", "account_management_url", diff --git a/twake/backend/node/yarn.lock b/twake/backend/node/yarn.lock index 0809e4ef..2d23c5f4 100644 --- a/twake/backend/node/yarn.lock +++ b/twake/backend/node/yarn.lock @@ -1456,6 +1456,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@okta/jwt-verifier@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@okta/jwt-verifier/-/jwt-verifier-3.0.1.tgz#10facad0424052e0cfd6477304cdc51d84ac60be" + integrity sha512-8rNvCxAcgGlSJH9NilhYTrbZjyTPPF0ThJUDF/NI8XV/4NcqOFdO946meFsXpV1oFdZP6ASH/j2GjVhU4cHpPQ== + dependencies: + jwks-rsa "^3.0.0" + njwt "^2.0.0" + "@redis/bloom@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.0.2.tgz#42b82ec399a92db05e29fffcdfd9235a5fc15cdf" @@ -1703,6 +1711,14 @@ resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652" integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/busboy@^0.2.3": version "0.2.4" resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-0.2.4.tgz#19922f8c7076ad6d47b2565da8c0a94c88776315" @@ -1740,6 +1756,13 @@ resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.36.tgz#bf53ca640f3a1a6a2072a9f33e02a44def07a40b" integrity sha512-EoAeT1MyFWh2BJvBDEFInY714bQBbHOAucqxqqhprhbBFqr+B7fuN5T9CJqUIGDzvwubnKKRqmSo6yPo0aSpNw== +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" @@ -1768,6 +1791,25 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.52.tgz" integrity sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ== +"@types/express-serve-static-core@^4.17.33": + version "4.17.33" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.14": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/fastify-multipart@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@types/fastify-multipart/-/fastify-multipart-0.7.0.tgz#fcbabfb35e69af0290a1c6c83199fc7e03855a51" @@ -1853,6 +1895,13 @@ dependencies: "@types/node" "*" +"@types/jsonwebtoken@^9.0.0": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#29b1369c4774200d6d6f63135bf3d1ba3ef997a4" + integrity sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw== + dependencies: + "@types/node" "*" + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -1870,6 +1919,11 @@ resolved "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -1926,6 +1980,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz" integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q== +"@types/node@^15.0.1": + version "15.14.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" + integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -1986,11 +2045,21 @@ dependencies: "@types/node" "*" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + "@types/random-useragent@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@types/random-useragent/-/random-useragent-0.3.1.tgz#1f0e1e08151f04d2c023ab77c8173dfa5b5049d2" integrity sha512-yVz7897JjjwS6V+lmxlu8pktzNnNWgtF9MGxfaRfI8o+hQageO9K+0hfwr8PspdFJIIRE9KkLqp4o5tlQqv/0w== +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/redis@^4.0.11": version "4.0.11" resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1" @@ -2010,6 +2079,14 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/serve-static@*": + version "1.15.1" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" + integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== + dependencies: + "@types/mime" "*" + "@types/node" "*" + "@types/sharp@^0.29.5": version "0.29.5" resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3" @@ -2732,7 +2809,7 @@ base64-arraybuffer@0.1.4: resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz" integrity sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg== -base64-js@^1.3.1: +base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3647,6 +3724,11 @@ crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -4031,7 +4113,7 @@ dynamic-dedupe@^0.3.0: dependencies: xtend "^4.0.0" -ecdsa-sig-formatter@1.0.11: +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== @@ -4213,6 +4295,11 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== +es6-promise@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -5572,6 +5659,18 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +idtoken-verifier@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/idtoken-verifier/-/idtoken-verifier-2.2.3.tgz#1758e9b0596f7036134938d63e107a72045622b8" + integrity sha512-hhpzB+MRgEvbwqzRLFdVbG55lKdXQVfeYEjAA2qu0UC72MSLeR0nX7P7rY5Dycz1aISHPOwq80hIPFoJ/+SItA== + dependencies: + base64-js "^1.5.1" + crypto-js "^4.1.1" + es6-promise "^4.2.8" + jsbn "^1.1.0" + unfetch "^4.2.0" + url-join "^4.0.1" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -6475,6 +6574,11 @@ join-component@^1.1.0: resolved "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz" integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ== +jose@^4.10.0, jose@^4.10.4: + version "4.13.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.13.1.tgz#449111bb5ab171db85c03f1bd2cb1647ca06db1c" + integrity sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ== + joycon@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" @@ -6500,6 +6604,11 @@ js-yaml@^4.0.0: dependencies: argparse "^2.0.1" +jsbn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsdom@^16.4.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -6651,6 +6760,18 @@ jwa@^1.4.1: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jwks-rsa@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-3.0.1.tgz#ba79ddca7ee7520f7bb26b942ef1aee91df8d7e4" + integrity sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw== + dependencies: + "@types/express" "^4.17.14" + "@types/jsonwebtoken" "^9.0.0" + debug "^4.3.4" + jose "^4.10.4" + limiter "^1.1.5" + lru-memoizer "^2.1.4" + jws@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" @@ -6747,6 +6868,11 @@ light-my-request@^4.2.0: process-warning "^1.0.0" set-cookie-parser "^2.4.1" +limiter@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" + integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" @@ -6776,6 +6902,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" @@ -6896,6 +7027,22 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@~4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + integrity sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw== + dependencies: + pseudomap "^1.0.1" + yallist "^2.0.0" + +lru-memoizer@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.2.0.tgz#b9d90c91637b4b1a423ef76f3156566691293df8" + integrity sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "~4.0.0" + lru_map@^0.3.3: version "0.3.3" resolved "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz" @@ -7329,6 +7476,15 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +njwt@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/njwt/-/njwt-2.0.0.tgz#da8b7ad995980de67b8069dad63949d2bd5df27d" + integrity sha512-1RcqirhCqThBEe4KO83pFg0wPBa1c9NiXNCrocD2EbZqb6ksWWDVnp/w/p0gsyUcVa05PhhaaPjs9rc/GLmdxQ== + dependencies: + "@types/node" "^15.0.1" + ecdsa-sig-formatter "^1.0.5" + uuid "^8.3.2" + node-abi@^3.3.0: version "3.22.0" resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz" @@ -7545,6 +7701,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz" @@ -7579,6 +7740,11 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +oidc-token-hash@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" + integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== + on-exit-leak-free@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" @@ -7610,6 +7776,16 @@ openapi-types@^10.0.0: resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-10.0.0.tgz" integrity sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ== +openid-client@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.4.0.tgz#77f1cda14e2911446f16ea3f455fc7c405103eac" + integrity sha512-hgJa2aQKcM2hn3eyVtN12tEA45ECjTJPXCgUh5YzTzy9qwapCvmDTVPWOcWVL0d34zeQoQ/hbG9lJhl3AYxJlQ== + dependencies: + jose "^4.10.0" + lru-cache "^6.0.0" + object-hash "^2.0.1" + oidc-token-hash "^5.0.1" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -8077,7 +8253,7 @@ ps-tree@^1.2.0: dependencies: event-stream "=3.3.4" -pseudomap@^1.0.2: +pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== @@ -9695,6 +9871,11 @@ undici@^3.3.6: resolved "https://registry.yarnpkg.com/undici/-/undici-3.3.6.tgz#06d3b97b7eeff46bce6f8a71079c09f64dd59dc1" integrity sha512-/j3YTZ5AobMB4ZrTY72mzM54uFUX32v0R/JRW9G2vOyF1uSKYAx+WT8dMsAcRS13TOFISv094TxIyWYk+WEPsA== +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -9801,6 +9982,11 @@ urix@^0.1.0: resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz" integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz" @@ -10148,7 +10334,7 @@ yallist@4.0.0, yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yallist@^2.1.2: +yallist@^2.0.0, yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== diff --git a/twake/frontend/public/favicon.ico b/twake/frontend/public/favicon.ico index 5c56ac5f..fd304422 100644 Binary files a/twake/frontend/public/favicon.ico and b/twake/frontend/public/favicon.ico differ diff --git a/twake/frontend/public/favicon.png b/twake/frontend/public/favicon.png index 23d60189..de1b6912 100644 Binary files a/twake/frontend/public/favicon.png and b/twake/frontend/public/favicon.png differ diff --git a/twake/frontend/public/index.html b/twake/frontend/public/index.html index 9ad1ae13..26e3785c 100644 --- a/twake/frontend/public/index.html +++ b/twake/frontend/public/index.html @@ -2,9 +2,9 @@ - Twake + Tdrive - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/twake/frontend/public/public/img/logo/logo-text-black.png b/twake/frontend/public/public/img/logo/logo-text-black.png new file mode 100644 index 00000000..5af3c80e Binary files /dev/null and b/twake/frontend/public/public/img/logo/logo-text-black.png differ diff --git a/twake/frontend/public/public/img/logo/logo-text-white.png b/twake/frontend/public/public/img/logo/logo-text-white.png new file mode 100644 index 00000000..c63f39e3 Binary files /dev/null and b/twake/frontend/public/public/img/logo/logo-text-white.png differ diff --git a/twake/frontend/public/public/img/primary-gradient.png b/twake/frontend/public/public/img/primary-gradient.png new file mode 100644 index 00000000..95d19790 Binary files /dev/null and b/twake/frontend/public/public/img/primary-gradient.png differ diff --git a/twake/frontend/public/public/img/twake-emoji/twake-calendar.png b/twake/frontend/public/public/img/twake-emoji/twake-calendar.png deleted file mode 100644 index 429c3157..00000000 Binary files a/twake/frontend/public/public/img/twake-emoji/twake-calendar.png and /dev/null differ diff --git a/twake/frontend/public/public/img/twake-emoji/twake-calendar.svg b/twake/frontend/public/public/img/twake-emoji/twake-calendar.svg deleted file mode 100644 index 6c7fd870..00000000 --- a/twake/frontend/public/public/img/twake-emoji/twake-calendar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/twake/frontend/public/public/img/twake-emoji/twake-drive.png b/twake/frontend/public/public/img/twake-emoji/twake-drive.png deleted file mode 100644 index c3a04ebf..00000000 Binary files a/twake/frontend/public/public/img/twake-emoji/twake-drive.png and /dev/null differ diff --git a/twake/frontend/public/public/img/twake-emoji/twake-drive.svg b/twake/frontend/public/public/img/twake-emoji/twake-drive.svg deleted file mode 100644 index 134458b9..00000000 --- a/twake/frontend/public/public/img/twake-emoji/twake-drive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/twake/frontend/public/public/img/twake-emoji/twake-task.svg b/twake/frontend/public/public/img/twake-emoji/twake-task.svg deleted file mode 100644 index 72ab7a80..00000000 --- a/twake/frontend/public/public/img/twake-emoji/twake-task.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/twake/frontend/public/public/img/twake-emoji/twake-tasks.png b/twake/frontend/public/public/img/twake-emoji/twake-tasks.png deleted file mode 100644 index d7f321ec..00000000 Binary files a/twake/frontend/public/public/img/twake-emoji/twake-tasks.png and /dev/null differ diff --git a/twake/frontend/src/app/app.tsx b/twake/frontend/src/app/app.tsx index 21840e61..1f3b34ec 100755 --- a/twake/frontend/src/app/app.tsx +++ b/twake/frontend/src/app/app.tsx @@ -4,8 +4,7 @@ import { Router } from 'react-router'; import { Switch, Route } from 'react-router-dom'; import { RecoilRoot } from 'recoil'; -import MobileRedirect from './views/mobile-redirect'; -import Integration from 'app/views/integration'; +import MobileRedirect from './components/mobile-redirect'; import RouterServices, { RouteType } from './features/router/services/router-service'; import ErrorBoundary from 'app/views/error/error-boundary'; import InitService from './features/global/services/init-service'; @@ -56,41 +55,39 @@ export default () => { - - - - {RouterServices.routes.map((route: RouteType, index: number) => ( - - route.options?.withErrorBoundary ? ( - - - - ) : ( - - ) - } - /> - ))} - { - { - RouterServices.replace( - `${ - RouterServices.pathnames.LOGIN - }?auto&${RouterServices.history.location.search.substr(1)}`, - ); - return
; - }} - /> - } - - - + + + {RouterServices.routes.map((route: RouteType, index: number) => ( + + route.options?.withErrorBoundary ? ( + + + + ) : ( + + ) + } + /> + ))} + { + { + RouterServices.replace( + `${ + RouterServices.pathnames.LOGIN + }?auto&${RouterServices.history.location.search.substr(1)}`, + ); + return
; + }} + /> + } + + ); diff --git a/twake/frontend/src/app/atoms/avatar/index.tsx b/twake/frontend/src/app/atoms/avatar/index.tsx index e41630fb..ae337dcd 100644 --- a/twake/frontend/src/app/atoms/avatar/index.tsx +++ b/twake/frontend/src/app/atoms/avatar/index.tsx @@ -15,7 +15,7 @@ interface AvatarProps extends React.InputHTMLAttributes { nogradient?: boolean; } -const sizes = { xl: 24, lg: 14, md: 11, sm: 9, xs: 6 }; +const sizes = { xl: 24, lg: 14, md: 10, sm: 9, xs: 6 }; const fontSizes = { xl: '2xl', lg: '2xl', md: 'lg', sm: 'md', xs: 'sm' }; export const getGradient = (name: string) => { diff --git a/twake/frontend/src/app/atoms/button/button.tsx b/twake/frontend/src/app/atoms/button/button.tsx index 685be3f0..2548a283 100644 --- a/twake/frontend/src/app/atoms/button/button.tsx +++ b/twake/frontend/src/app/atoms/button/button.tsx @@ -3,7 +3,7 @@ import React from 'react'; import _ from 'lodash'; interface ButtonProps extends React.ButtonHTMLAttributes { - theme?: 'primary' | 'secondary' | 'danger' | 'default' | 'outline' | 'dark'; + theme?: 'primary' | 'secondary' | 'danger' | 'default' | 'outline' | 'dark' | 'white'; size?: 'md' | 'lg' | 'sm'; icon?: (props: any) => JSX.Element; iconSize?: 'md' | 'lg'; @@ -19,18 +19,22 @@ export const Button = (props: ButtonProps) => { if (props.theme === 'secondary') className = - 'text-blue-500 bg-blue-100 hover:bg-blue-200 active:bg-blue-300 border-transparent '; + 'text-blue-500 bg-blue-500 hover:bg-opacity-30 active:bg-opacity-50 bg-opacity-25 border-transparent '; if (props.theme === 'danger') className = 'text-white bg-rose-500 hover:bg-rose-600 active:bg-rose-700 border-transparent '; if (props.theme === 'default') className = - 'text-black dark:text-white bg-white dark:bg-zinc-800 dark:hover:bg-zinc-700 dark:active:bg-zinc-900 hover:bg-gray-50 active:bg-gray-200 border-gray-300'; + 'text-black dark:text-white bg-white dark:bg-zinc-800 dark:hover:bg-zinc-700 dark:active:bg-zinc-900 hover:bg-zinc-50 active:bg-zinc-200 border-zinc-300'; + + if (props.theme === 'white') + className = + 'text-black dark:text-white dark:hover:bg-zinc-700 dark:active:bg-zinc-900 hover:bg-zinc-50 active:bg-zinc-200 border-transparent'; if (props.theme === 'outline') className = - 'text-blue-500 bg-white dark:bg-zinc-800 dark:hover:bg-zinc-700 dark:active:bg-zinc-900 hover:bg-gray-50 active:bg-gray-200 border-blue-500'; + 'text-blue-500 bg-white dark:bg-zinc-800 dark:hover:bg-zinc-700 dark:active:bg-zinc-900 hover:bg-zinc-50 active:bg-zinc-200 border-blue-500'; if (props.theme === 'dark') className = @@ -38,12 +42,12 @@ export const Button = (props: ButtonProps) => { if (disabled) className += ' opacity-50 pointer-events-none'; - if (props.size === 'lg') className = className + ' text-lg h-11'; + if (props.size === 'lg') className = className + ' text-lg h-10'; else if (props.size === 'sm') className = className + ' text-sm h-7 px-3'; else className = className + ' text-base h-9'; if (!props.children) { - if (props.size === 'lg') className = className + ' w-11 !p-0 justify-center'; + if (props.size === 'lg') className = className + ' w-10 !p-0 justify-center'; else if (props.size === 'sm') className = className + ' w-7 !p-0 justify-center'; else className = className + ' w-9 !p-0 justify-center'; } diff --git a/twake/frontend/src/app/atoms/input/input-text.tsx b/twake/frontend/src/app/atoms/input/input-text.tsx index 5b35e1a6..49110e08 100644 --- a/twake/frontend/src/app/atoms/input/input-text.tsx +++ b/twake/frontend/src/app/atoms/input/input-text.tsx @@ -20,13 +20,13 @@ interface InputProps } const baseInputClassName = - 'tw-input block w-full rounded-md focus:ring-1 focus:ring-blue-500 z-0 focus:z-10 dark:text-white text-black '; + 'tw-input block w-full rounded-md focus:ring-1 focus:ring-blue-500 z-0 focus:z-10 dark:text-white text-black text-base '; export const defaultInputClassName = (theme: 'plain' | 'outline' = 'plain') => { return ( baseInputClassName + (theme === 'plain' - ? 'bg-zinc-200 border-zinc-200 dark:bg-zinc-800 dark:border-zinc-800' + ? 'bg-zinc-100 border-zinc-100 dark:bg-zinc-800 dark:border-zinc-800' : 'bg-zinc-50 border-zinc-300 dark:bg-zinc-800 dark:border-zinc-700') ); }; @@ -47,7 +47,7 @@ export const Input = (props: InputProps) => { inputClassName = inputClassName + (props.disabled ? ' opacity-75' : ''); if (!props.multiline) { - if (props.size === 'lg') inputClassName = inputClassName + ' h-11'; + if (props.size === 'lg') inputClassName = inputClassName + ' h-10 pl-10'; else if (props.size === 'sm') inputClassName = inputClassName + ' h-7'; else inputClassName = inputClassName + ' h-9'; } diff --git a/twake/frontend/src/app/atoms/text/index.tsx b/twake/frontend/src/app/atoms/text/index.tsx index 6f71c559..d078f372 100644 --- a/twake/frontend/src/app/atoms/text/index.tsx +++ b/twake/frontend/src/app/atoms/text/index.tsx @@ -52,13 +52,13 @@ const Text = (props: TextProps) => { defaultClassName = 'text-sm font-normal' + ' ' + - (props.noColor ? '' : 'text-zinc-400 dark:text-white dark:opacity-50'); + (props.noColor ? '' : 'text-zinc-500 dark:text-white dark:opacity-50'); break; case 'info-small': defaultClassName = 'text-xs font-normal' + ' ' + - (props.noColor ? '' : 'text-zinc-400 dark:text-white dark:opacity-50'); + (props.noColor ? '' : 'text-zinc-500 dark:text-white dark:opacity-50'); break; } diff --git a/twake/frontend/src/app/components/configurators/config-body-layer.jsx b/twake/frontend/src/app/components/configurators/config-body-layer.jsx deleted file mode 100755 index 1dbf53d6..00000000 --- a/twake/frontend/src/app/components/configurators/config-body-layer.jsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; - -import ConfiguratorsManager from 'app/deprecated/Configurators/ConfiguratorsManager.js'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import CloseIcon from '@material-ui/icons/CloseOutlined'; -import './configurators.scss'; -import Blocks from '../../views/applications/messages/message/parts/Blocks'; - -/* - Where the configurators will be displayed, this component should be in app.js (menus should be over all elements of the page) -*/ -export default class ConfigBodyLayer extends React.Component { - constructor() { - super(); - this.state = {}; - ConfiguratorsManager.addListener(this); - } - componentWillUnmount() { - ConfiguratorsManager.removeListener(this); - clearTimeout(this.loading_interaction_timeout); - } - UNSAFE_componentWillMount() { - this.generateData(); - } - shouldComponentUpdate(nextProps, nextState) { - this.generateData(); - var string = JSON.stringify(this.configurator); - if (string != this.saved) { - nextState.loading_interaction = false; - clearTimeout(this.loading_interaction_timeout); - this.saved = string; - return true; - } - return nextState.loading_interaction ? true : false; - } - generateData() { - if (ConfiguratorsManager.configurator_order.length == 0) { - this.configurator = null; - } - this.configurator = - ConfiguratorsManager.currentConfigurators[ - ConfiguratorsManager.configurator_order[ConfiguratorsManager.configurator_order.length - 1] - ]; - } - onAction(type, id, context, passives) { - if (type == 'interactive_action') { - this.setState({ loading_interaction: true }); - clearTimeout(this.loading_interaction_timeout); - this.loading_interaction_timeout = setTimeout(() => { - this.setState({ loading_interaction: false }); - }, 5000); - var app_id = this.configurator.app.id; - var ntype = 'interactive_configuration_action'; - var event = id; - var data = { - interactive_context: context, - form: passives, - hidden_data: this.configurator.hidden_data, - configurator_id: this.configurator.id, - }; - WorkspacesApps.notifyApp(app_id, ntype, event, data); - } - } - render() { - if (!this.configurator) { - return ''; - } - - return ( -
-
-
-
-
-
-
{this.configurator.app.identity?.name}
- - { - ConfiguratorsManager.closeConfigurator(this.configurator.app); - }} - /> -
-
- - this.onAction(type, id, context, passives, evt) - } - allowAdvancedBlocks={true} - /> -
-
-
-
-
- ); - } -} diff --git a/twake/frontend/src/app/components/configurators/configurators.scss b/twake/frontend/src/app/components/configurators/configurators.scss deleted file mode 100755 index df002a59..00000000 --- a/twake/frontend/src/app/components/configurators/configurators.scss +++ /dev/null @@ -1,60 +0,0 @@ -.config_modal { - height: 100%; - width: 100%; - background: rgba(255, 255, 255, 0.5); - position: absolute; - z-index: 200; - top: 0; - - .modal { - width: 0px; - height: 0px; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto; - .component { - background: white; - border-radius: var(--border-radius-base); - box-shadow: var(--box-shadow-base); - position: relative; - display: inline-flex; - flex-direction: column; - transform: translateX(-50%) translateY(-50%); - .header { - padding: 15px; - border-bottom: 1px solid #ddd; - display: flex; - - .app_logo { - height: 30px; - width: 30px; - background-size: contain; - background-position: center; - border-radius: var(--border-radius-base); - margin-right: 5px; - } - .app_name { - line-height: 30px; - flex: auto; - } - .close { - margin: 2px; - opacity: 0.5; - cursor: pointer; - &:hover { - opacity: 1; - } - } - } - .content { - flex: 1; - overflow: auto; - padding: 15px; - box-sizing: border-box; - } - } - } -} diff --git a/twake/frontend/src/app/views/desktop-redirect/index.tsx b/twake/frontend/src/app/components/desktop-redirect/index.tsx similarity index 100% rename from twake/frontend/src/app/views/desktop-redirect/index.tsx rename to twake/frontend/src/app/components/desktop-redirect/index.tsx diff --git a/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx b/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx index 10ffe838..b913a3c7 100644 --- a/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx +++ b/twake/frontend/src/app/components/edit-channel/channel-settings-menu.tsx @@ -29,7 +29,7 @@ import RouterServices from 'app/features/router/services/router-service'; import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; import workspaceUserRightsService from 'app/features/workspaces/services/workspace-user-rights-service'; import Block from 'app/molecules/grouped-rows/base'; -import { addUrlTryDesktop } from 'app/views/desktop-redirect'; +import { addUrlTryDesktop } from 'app/components/desktop-redirect'; import { useState } from 'react'; export const ChannelSettingsMenu = (props: { diff --git a/twake/frontend/src/app/components/invitation/parts/invitation-input-bulk.tsx b/twake/frontend/src/app/components/invitation/parts/invitation-input-bulk.tsx index fe3b04b7..a7e7525d 100644 --- a/twake/frontend/src/app/components/invitation/parts/invitation-input-bulk.tsx +++ b/twake/frontend/src/app/components/invitation/parts/invitation-input-bulk.tsx @@ -64,7 +64,7 @@ export default (): React.ReactElement => { style={{ maxHeight: 'calc(42vh - 98px)', minHeight: 'calc(42vh - 98px)' }} options={{ suppressScrollX: true, suppressScrollY: false }} > - { members_limit_reached && } + {members_limit_reached && } { const { user } = useCurrentUser(); const maxUnverifiedDays = - InitService.server_infos?.configuration?.accounts?.console?.max_unverified_days || 7; + InitService.server_infos?.configuration?.accounts?.remote?.max_unverified_days || 7; const oneDay = 1000 * 60 * 60 * 24; const periodLimit = (user?.created_at || 0) + maxUnverifiedDays * oneDay; const daysLeft = Math.ceil((periodLimit - Date.now()) / oneDay); - if (!user || InitService.server_infos?.configuration?.accounts?.type !== 'console') { + if (!user || InitService.server_infos?.configuration?.accounts?.type !== 'remote') { return <>; } const showBlockedModal = () => { - if (InitService.server_infos?.configuration?.accounts?.type === 'console') + if (InitService.server_infos?.configuration?.accounts?.type === 'remote') return ModalManager.open( , { @@ -34,7 +34,7 @@ const AccountStatusComponent = (): JSX.Element => { }; const showUnverifiedModal = () => { - if (InitService.server_infos?.configuration?.accounts?.type === 'console') + if (InitService.server_infos?.configuration?.accounts?.type === 'remote') return ModalManager.open( , { diff --git a/twake/frontend/src/app/components/on-boarding/company-status-component.tsx b/twake/frontend/src/app/components/on-boarding/company-status-component.tsx index d9ad1c9f..fb3a09db 100644 --- a/twake/frontend/src/app/components/on-boarding/company-status-component.tsx +++ b/twake/frontend/src/app/components/on-boarding/company-status-component.tsx @@ -18,7 +18,7 @@ const CompanyStatusComponent = (): JSX.Element => { const workspace = DepreciatedCollections.get('workspaces').find(workspaceId); useEffect(() => { - if (InitService.server_infos?.configuration?.accounts?.type === 'console') { + if (InitService.server_infos?.configuration?.accounts?.type === 'remote') { isNewAccount(); } diff --git a/twake/frontend/src/app/components/quoted-message/quoted-message.tsx b/twake/frontend/src/app/components/quoted-message/quoted-message.tsx deleted file mode 100644 index c3b04b46..00000000 --- a/twake/frontend/src/app/components/quoted-message/quoted-message.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import useRouterCompany from 'app/features/router/hooks/use-router-company'; -import MessageQuote from 'app/molecules/message-quote'; -import { useUser } from 'app/features/users/hooks/use-user'; -import { useMessageQuoteReply } from 'app/features/messages/hooks/use-message-quote-reply'; -import useRouterChannel from 'app/features/router/hooks/use-router-channel'; -import User from 'app/features/users/services/current-user-service'; -import { gotoMessage } from 'src/utils/messages'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import QuotedContent from 'app/molecules/quoted-content'; -import { NodeMessage } from 'app/features/messages/types/message'; - -type PropsType = { - closable?: boolean; - onClose?: () => void; -}; - -export default ({ closable = true, onClose }: PropsType): React.ReactElement => { - const companyId = useRouterCompany(); - const channelId = useRouterChannel(); - const workspaceId = useRouterWorkspace(); - const { message: quotedMessageId } = useMessageQuoteReply(channelId); - const quotedMessage = useMessage({ - companyId, - threadId: quotedMessageId, - id: quotedMessageId, - }); - - if (!quotedMessage.message) { - return <>; - } - - const { message } = quotedMessage; - const author = useUser(message.user_id); - const authorName = author ? User.getFullName(author) : 'Anonymous'; - const deleted = message.subtype === 'deleted'; - - const quotedContent = ( - - ); - - return ( - gotoMessage(message, companyId, channelId, workspaceId)} - /> - ); -}; diff --git a/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx b/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx index 4e444a1b..62695f8f 100644 --- a/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx +++ b/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx @@ -25,7 +25,7 @@ import { useRecoilValue, useRecoilState } from 'recoil'; import { openDriveItem, onDriveItemDownloadClick } from '../common'; import ResultContext from './result-context'; import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; -import { DriveCurrentFolderAtom } from 'app/views/applications/drive/browser'; +import { DriveCurrentFolderAtom } from 'app/views/client/body/drive/browser'; import { FolderIcon } from '@heroicons/react/solid'; export default (props: { driveItem: DriveItem & { user?: UserType } }) => { diff --git a/twake/frontend/src/app/components/uploads/upload-zone.tsx b/twake/frontend/src/app/components/uploads/upload-zone.tsx index b7fb3332..ef6e531b 100755 --- a/twake/frontend/src/app/components/uploads/upload-zone.tsx +++ b/twake/frontend/src/app/components/uploads/upload-zone.tsx @@ -49,6 +49,8 @@ export default class UploadZone extends React.Component { this.file_input.style.left = '-10000px'; this.file_input.style.width = '100px'; this.file_input.multiple = this.props.multiple ? true : false; + this.file_input.directory = this.props.multiple ? true : false; + this.file_input.webkitdirectory = this.props.multiple ? true : false; this.setCallback(); diff --git a/twake/frontend/src/app/deprecated/workspaces/workspaces.jsx b/twake/frontend/src/app/deprecated/workspaces/workspaces.jsx index 2ae0fb74..cd64864c 100755 --- a/twake/frontend/src/app/deprecated/workspaces/workspaces.jsx +++ b/twake/frontend/src/app/deprecated/workspaces/workspaces.jsx @@ -1,26 +1,21 @@ -import React from 'react'; -import $ from 'jquery'; -import Observable from 'app/deprecated/CollectionsV1/observable.js'; -import popupManager from 'app/deprecated/popupManager/popupManager.js'; -import PopupManager from 'app/deprecated/popupManager/popupManager.js'; -import Api from 'app/features/global/framework/api-service'; -import ws from 'app/deprecated/websocket/websocket.js'; import DepreciatedCollections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; +import Observable from 'app/deprecated/CollectionsV1/observable.js'; +import { default as popupManager, default as PopupManager } from 'app/deprecated/popupManager/popupManager.js'; +import ws from 'app/deprecated/websocket/websocket.js'; import Groups from 'app/deprecated/workspaces/groups.js'; -import LocalStorage from 'app/features/global/framework/local-storage-service'; -import workspacesUsers from 'app/features/workspace-members/services/workspace-members-service'; -import WindowService from 'app/features/global/utils/window'; import workspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import RouterServices from 'app/features/router/services/router-service'; -import NoWorkspaces from 'app/views/client/workspaces-bar/components/NoWorkspaces/NoWorkspaces'; -import NoCompanies from 'app/views/client/workspaces-bar/components/NoWorkspaces/NoCompanies'; -import loginService from 'app/features/auth/login-service'; -import Globals from 'app/features/global/services/globals-twake-app-service'; import JWTStorage from 'app/features/auth/jwt-storage-service'; +import loginService from 'app/features/auth/login-service'; import ConsoleService from 'app/features/console/services/console-service'; -import WorkspaceAPIClient from '../../features/workspaces/api/workspace-api-client'; +import Api from 'app/features/global/framework/api-service'; +import LocalStorage from 'app/features/global/framework/local-storage-service'; import Logger from 'app/features/global/framework/logger-service'; -import UserAPIClient from 'app/features/users/api/user-api-client'; +import Globals from 'app/features/global/services/globals-twake-app-service'; +import WindowService from 'app/features/global/utils/window'; +import RouterServices from 'app/features/router/services/router-service'; +import workspacesUsers from 'app/features/workspace-members/services/workspace-members-service'; +import $ from 'jquery'; +import WorkspaceAPIClient from '../../features/workspaces/api/workspace-api-client'; class Workspaces extends Observable { constructor() { @@ -90,7 +85,6 @@ class Workspaces extends Observable { openNoWorkspacesPage() { this.showNoWorkspacesPage = true; this.notify(); - popupManager.open(, false, 'no_workspace_parameters'); } closeNoWorkspacesPage() { @@ -102,7 +96,6 @@ class Workspaces extends Observable { openNoCompaniesPage() { this.showNoCompaniesPage = true; this.notify(); - popupManager.open(, false, 'no_companies_parameters'); } closeNoCompaniesPage() { diff --git a/twake/frontend/src/app/environment/types.ts b/twake/frontend/src/app/environment/types.ts index 34a55e03..c3614c10 100644 --- a/twake/frontend/src/app/environment/types.ts +++ b/twake/frontend/src/app/environment/types.ts @@ -1,6 +1,6 @@ export type EnvironmentType = { env_dev: boolean; - env_dev_auth?: 'internal' | 'console'; + env_dev_auth?: 'internal' | 'remote'; api_root_url: string; front_root_url: string; diff --git a/twake/frontend/src/app/features/auth/auth-service.ts b/twake/frontend/src/app/features/auth/auth-service.ts index 273935c6..9c41bb0c 100644 --- a/twake/frontend/src/app/features/auth/auth-service.ts +++ b/twake/frontend/src/app/features/auth/auth-service.ts @@ -20,7 +20,7 @@ import Application from 'app/features/applications/services/application-service' import LocalStorage from 'app/features/global/framework/local-storage-service'; import Globals from 'app/features/global/services/globals-twake-app-service'; -type AccountType = 'console' | 'internal'; +type AccountType = 'remote' | 'internal'; export type LoginState = | '' | 'app' @@ -60,7 +60,7 @@ class AuthService { if (Globals.environment.env_dev_auth) accountType = Globals.environment.env_dev_auth; - if (accountType === 'console') { + if (accountType === 'remote') { this.provider = new OIDCAuthProviderService(config as ConsoleConfiguration); } else if (accountType === 'internal') { this.provider = new InternalAuthProviderService(config as InternalConfiguration); diff --git a/twake/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts b/twake/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts index 574080f4..41800092 100644 --- a/twake/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts +++ b/twake/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts @@ -14,7 +14,6 @@ import LocalStorage from 'app/features/global/framework/local-storage-service'; const OIDC_CALLBACK_URL = '/oidccallback'; const OIDC_SIGNOUT_URL = '/signout'; -const OIDC_CLIENT_ID = 'twake'; @TwakeService('OIDCAuthProvider') export default class OIDCAuthProviderService @@ -45,13 +44,15 @@ export default class OIDCAuthProviderService Oidc.Log.logger = Logger.getLogger('OIDCClient'); Oidc.Log.level = EnvironmentService.isProduction() ? Oidc.Log.WARN : Oidc.Log.DEBUG; + const audience = ['https://dev-mxb6x0f2.eu.auth0.com/api/v2/']; + this.userManager = new Oidc.UserManager({ userStore: new Oidc.WebStorageStateStore({ store: window.localStorage }), authority: this.configuration?.authority || environment.api_root_url, - client_id: this.configuration?.client_id || OIDC_CLIENT_ID, + client_id: this.configuration?.client_id, redirect_uri: getAsFrontUrl(OIDC_CALLBACK_URL), response_type: 'code', - scope: 'openid profile email address phone offline_access', + scope: 'openid profile email address phone offline_access ' + audience.join(' '), post_logout_redirect_uri: getAsFrontUrl(OIDC_SIGNOUT_URL), //silent_redirect_uri: getAsFrontUrl(OIDC_SILENT_URL), automaticSilentRenew: true, @@ -236,7 +237,10 @@ export default class OIDCAuthProviderService this.logger.info('getJWTFromOidcToken, user expired'); } - ConsoleService.getNewAccessToken({ access_token: user.access_token }, callback); + ConsoleService.getNewAccessToken( + { id_token: user.id_token, access_token: user.access_token }, + callback, + ); } signinRedirect() { diff --git a/twake/frontend/src/app/features/console/services/console-service.ts b/twake/frontend/src/app/features/console/services/console-service.ts index f0abf93c..53c65644 100644 --- a/twake/frontend/src/app/features/console/services/console-service.ts +++ b/twake/frontend/src/app/features/console/services/console-service.ts @@ -109,7 +109,7 @@ class ConsoleService { * @param callback */ public getNewAccessToken( - currentToken: { access_token: string }, + currentToken: { access_token: string; id_token: string }, callback: (err?: Error, access_token?: JWTDataType) => void, ): void { this.logger.debug( @@ -117,7 +117,7 @@ class ConsoleService { ); Api.post( '/internal/services/console/v1/login', - { remote_access_token: currentToken.access_token }, + { oidc_id_token: currentToken.id_token }, (response: { access_token: JWTDataType; message: string; diff --git a/twake/frontend/src/app/features/global/framework/logger-service.ts b/twake/frontend/src/app/features/global/framework/logger-service.ts index 973354c0..1996cbcc 100644 --- a/twake/frontend/src/app/features/global/framework/logger-service.ts +++ b/twake/frontend/src/app/features/global/framework/logger-service.ts @@ -13,7 +13,7 @@ prefix.apply(log, { return level.toUpperCase(); }, nameFormatter(name) { - return name || 'Twake'; + return name || 'Tdrive'; }, timestampFormatter(date) { return date.toISOString(); diff --git a/twake/frontend/src/app/features/global/services/init-service.ts b/twake/frontend/src/app/features/global/services/init-service.ts index 9fee3dd6..52e39555 100644 --- a/twake/frontend/src/app/features/global/services/init-service.ts +++ b/twake/frontend/src/app/features/global/services/init-service.ts @@ -38,14 +38,15 @@ export type ServerInfoType = null | { help_url: string | null; pricing_plan_url: string | null; app_download_url: string | null; + app_grid: { logo: string; name: string; url: string }[]; mobile: { mobile_redirect: string; mobile_appstore: string; mobile_googleplay: string; }; accounts: { - type: 'console' | 'internal'; - console?: ConsoleConfiguration; + type: 'remote' | 'internal'; + remote?: ConsoleConfiguration; internal?: InternalConfiguration; }; }; @@ -68,7 +69,7 @@ class InitService extends Observable { companyId = companyId || WorkspaceService.currentGroupId; const identity_provider_id = getCompany(companyId || '')?.identity_provider_id || getCompany(companyId || '')?.id; - return (this.server_infos?.configuration?.accounts?.console?.[link] || '').replace( + return (this.server_infos?.configuration?.accounts?.remote?.[link] || '').replace( /\{company_id\}/gm, identity_provider_id, ); diff --git a/twake/frontend/src/app/features/global/utils/window.ts b/twake/frontend/src/app/features/global/utils/window.ts index bb7eb49b..fea70c55 100755 --- a/twake/frontend/src/app/features/global/utils/window.ts +++ b/twake/frontend/src/app/features/global/utils/window.ts @@ -3,7 +3,7 @@ import { capitalize } from 'lodash'; import Globals from 'app/features/global/services/globals-twake-app-service'; class WindowState { - public readonly app_name: string = 'Twake'; + public readonly app_name: string = 'Tdrive'; public prefix = ''; public suffix = ''; diff --git a/twake/frontend/src/app/features/router/services/router-service.ts b/twake/frontend/src/app/features/router/services/router-service.ts index aa70a40f..2df7b5f7 100644 --- a/twake/frontend/src/app/features/router/services/router-service.ts +++ b/twake/frontend/src/app/features/router/services/router-service.ts @@ -8,7 +8,7 @@ import Login from 'app/views/login/login'; import Logout from 'app/views/login/logout'; import Error from 'app/views/error'; import Join from 'app/views/join'; -import PublicMainView from 'app/views/applications/drive/shared'; +import PublicMainView from 'app/views/client/body/drive/shared'; import Observable from '../../../deprecated/Observable/Observable'; import { getWorkspacesByCompany } from 'app/features/workspaces/state/workspace-list'; diff --git a/twake/frontend/src/app/molecules/grouped-rows/base/index.tsx b/twake/frontend/src/app/molecules/grouped-rows/base/index.tsx index 282f7fa4..7909ed1b 100644 --- a/twake/frontend/src/app/molecules/grouped-rows/base/index.tsx +++ b/twake/frontend/src/app/molecules/grouped-rows/base/index.tsx @@ -30,8 +30,8 @@ export default function Block(props: BlockProps) {
-
- {props.subtitle} +
+ {props.subtitle}
{props.subtitle_suffix}
diff --git a/twake/frontend/src/app/molecules/quoted-content/index.tsx b/twake/frontend/src/app/molecules/quoted-content/index.tsx deleted file mode 100644 index 7ac6623a..00000000 --- a/twake/frontend/src/app/molecules/quoted-content/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { - FileTypeArchiveIcon, - FileTypeDocumentIcon, - FileTypePdfIcon, - FileTypeSlidesIcon, - FileTypeSpreadsheetIcon, - FileTypeUnknownIcon, -} from 'app/atoms/icons-colored'; -import { Base } from 'app/atoms/text'; -import fileUploadApiClient from 'app/features/files/api/file-upload-api-client'; -import Languages from 'app/features/global/services/languages-service'; -import { useMessage, useSetMessage } from 'app/features/messages/hooks/use-message'; -import { NodeMessage } from 'app/features/messages/types/message'; -import { MessageContext } from 'app/views/applications/messages/message/message-with-replies'; -import MessageContent from 'app/views/applications/messages/message/parts/MessageContent'; -import React, { useContext, useEffect } from 'react'; - -type PropsType = { - message: NodeMessage['quote_message']; -}; - -export const useQuotedMessage = ( - message: NodeMessage, - context: { - companyId: string; - workspaceId: string; - channelId: string; - threadId: string; - id: string; - }, -) => { - const quotedMessageFromStore = useMessage({ - ...context, - threadId: message.quote_message?.thread_id as string, - id: message.quote_message?.id as string, - }).message; - - const quotedMessage = { - ...message.quote_message, - ...(quotedMessageFromStore || message.quote_message), - } as NodeMessage['quote_message']; - - const setMessage = useSetMessage(quotedMessage?.company_id || context.companyId); - useEffect(() => { - if (!quotedMessageFromStore?.id && quotedMessage?.id) { - setMessage(quotedMessage); - } - }, []); - - return quotedMessage; -}; - -export default ({ message }: PropsType): React.ReactElement => { - const context = useContext(MessageContext); - - if (!message) { - return <>; - } - - const attachmentType = fileUploadApiClient.mimeToType(message.files?.[0]?.metadata?.mime || ''); - - if (context.channelId && message.channel_id !== context.channelId) { - return ( - <> - - - - - ); - } - - return ( - <> - {message.text && message.text.length ? ( - - {message.text} - - ) : ( -
- )} - {!!message.files?.length && - (message.files?.length === 1 ? ( -
- <> - {attachmentType === 'archive' ? ( - - ) : attachmentType === 'pdf' ? ( - - ) : attachmentType === 'document' ? ( - - ) : attachmentType === 'spreadsheet' ? ( - - ) : attachmentType === 'slides' ? ( - - ) : ( - - )} - - {message.files?.[0].metadata?.name} -
- ) : ( -
- - - {Languages.t('molecules.quoted_content.attachements', [message.files?.length])} - -
- ))} - - ); -}; diff --git a/twake/frontend/src/app/molecules/tabs/index.tsx b/twake/frontend/src/app/molecules/tabs/index.tsx index 3ee05c02..f88e854a 100644 --- a/twake/frontend/src/app/molecules/tabs/index.tsx +++ b/twake/frontend/src/app/molecules/tabs/index.tsx @@ -21,12 +21,14 @@ export default function Tab(props: TabsProps) { return ( <> -
+
{props.tabs.map((tab, idx) => { const cl = defaultTabClassName + - (idx === props.selected ? activeTabClassName : inactiveTabClassName) - + props.parentClassName; + (idx === props.selected ? activeTabClassName : inactiveTabClassName) + + props.parentClassName; return (
props.onClick(idx)}> {tab} diff --git a/twake/frontend/src/app/styles/ui.less b/twake/frontend/src/app/styles/ui.less index 0011fb45..23662cde 100755 --- a/twake/frontend/src/app/styles/ui.less +++ b/twake/frontend/src/app/styles/ui.less @@ -6,7 +6,7 @@ html { color: var(--black); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: var(--secondary) !important; + background-color: #FFF; } #root { diff --git a/twake/frontend/src/app/views/applications/calendar/calendar-content.jsx b/twake/frontend/src/app/views/applications/calendar/calendar-content.jsx deleted file mode 100755 index e384c7fe..00000000 --- a/twake/frontend/src/app/views/applications/calendar/calendar-content.jsx +++ /dev/null @@ -1,850 +0,0 @@ -import React, { Component, useState } from 'react'; - -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import Languages from 'app/features/global/services/languages-service'; -import UserService from 'app/features/users/services/current-user-service'; -import CalendarService from 'app/deprecated/Apps/Calendar/Calendar.js'; -import LocalStorage from 'app/features/global/framework/local-storage-service'; - -import ModalManager from 'app/components/modal/modal-manager'; - -import Icon from 'app/components/icon/icon.jsx'; -import moment from 'moment'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import FullCalendar from './full-calendar/full-calendar.jsx'; -import EventDetails from './modals/EventDetails.jsx'; -import EventCreation from './modals/EventCreation.jsx'; -import EventModification from './modals/EventModification.jsx'; -import CalendarEditor from './modals/CalendarEditor.jsx'; -import Menu from 'components/menus/menu.jsx'; -import DayPicker from 'components/calendar/day-picker/day-picker.jsx'; -import AlertManager from 'app/features/global/services/alert-manager-service'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import WorkspacesService from 'app/deprecated/workspaces/workspaces.jsx'; -import popupManager from 'app/deprecated/popupManager/popupManager.js'; -import ConnectorsListManager from 'components/connectors-list-manager/connectors-list-manager.jsx'; -import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service'; -import Checkbox from 'app/components/inputs/deprecated_checkbox.jsx'; -import InputWithClipBoard from 'components/input-with-clip-board/input-with-clip-board.jsx'; -import Select from 'components/select/select.jsx'; -import WorkspaceParameter from 'app/views/client/popup/WorkspaceParameter/WorkspaceParameter.jsx'; -import UnconfiguredTab from './unconfigured-tab.jsx'; -import RouterService from 'app/features/router/services/router-service'; -import MainPlus from 'components/main-plus/main-plus.jsx'; -import { - getCompanyApplication as getApplication, - getCompanyApplications, -} from 'app/features/applications/state/company-applications'; - -import './calendar.scss'; - -const ExportView = props => { - const [export_my_calendar, set_export_my_calendar] = useState(props.values.export_my_calendar); - const [export_workspace_calendar, set_export_workspace_calendar] = useState( - props.values.export_workspace_calendar, - ); - return ( -
- { - set_export_my_calendar(value); - props.onChange(export_my_calendar, export_workspace_calendar); - }} - /> - { - set_export_workspace_calendar(value); - props.onChange(export_my_calendar, export_workspace_calendar); - }} - /> -
- ); -}; - -export default class Calendar extends Component { - constructor(props) { - super(props); - - this.state = { - i18n: Languages, - view: 'timeGridWeek', - filter: 'workspace', - preview: null, - export_my_calendar: true, - export_workspace_calendar: true, - new_calendar: {}, - }; - CalendarService.filter_mode = this.state.filter; - - this.props = props; - const { workspaceId, channelId } = RouterService.getStateFromRoute(); - - this.loaded_date_range = {}; - this.calendar_collection_key = 'calendar_' + workspaceId; - - this.setLoadedRange( - 'both', - moment(CalendarService.date).startOf('month').toDate().getTime() / 1000 - 60 * 60 * 24 * 31, - moment(CalendarService.date).startOf('month').toDate().getTime() / 1000 + - 2 * 60 * 60 * 24 * 31, - ); - - Collections.get('calendars').addListener(this); - Collections.get('calendars').addSource( - { - http_base_url: 'calendar/calendar', - http_options: { - channel_id: channelId, - workspace_id: workspaceId, - }, - websockets: [ - { - uri: 'calendars/' + workspaceId, - options: { type: 'calendar' }, - }, - ], - }, - this.calendar_collection_key, - () => { - this.onFirstLoad(); - }, - ); - - if (Collections.get('calendars').did_load_first_time[this.calendar_collection_key]) { - this.onFirstLoad(); - } - - Collections.get('events').addListener(this); - Languages.addListener(this); - CalendarService.addListener(this); - } - onFirstLoad() { - const { workspaceId, channelId } = RouterService.getStateFromRoute(); - var calendar_list = Collections.get('calendars') - .findBy({ workspace_id: workspaceId }) - .map(cal => { - return { - calendar_id: cal.id, - workspace_id: cal.workspace_id, - }; - }); - if (this.props.tab != null && this.props.tab.configuration.calendars) { - this.allowed_ids = this.props.tab.configuration.calendars.map(c => c.calendar_id); - calendar_list = calendar_list.filter(c => this.allowed_ids.indexOf(c.calendar_id) >= 0); - } - Collections.get('events').addSource( - { - http_base_url: 'calendar/event', - http_options: { - channel_id: channelId, - after_ts: - this.loaded_date_range['both'].min || new Date().getTime() / 1000 - 24 * 60 * 60 * 60, - before_ts: - this.loaded_date_range['both'].max || new Date().getTime() / 1000 + 24 * 60 * 60 * 60, - calendar_list: calendar_list, - mode: 'both', - }, - websockets: [ - { - uri: 'calendar_events/' + workspaceId, - options: { type: 'event' }, - }, - { - uri: 'calendar_events/user/' + UserService.getCurrentUserId(), - options: { type: 'event' }, - }, - ], - }, - this.calendar_collection_key, - ); - } - setLoadedRange(key, min, max) { - if (!this.loaded_date_range[key]) { - this.loaded_date_range[key] = {}; - } - this.loaded_date_range[key].min = min; - this.loaded_date_range[key].max = max; - this.loaded_date_range[key].last_updated = new Date(); - } - - componentDidMount() { - const view = LocalStorage.getItem('calendar_view'); - - if (view && this.calendar) { - this.setState({ view }); - this.calendar.view(view); - } - } - - componentWillUnmount() { - Collections.get('events').removeListener(this); - Collections.get('events').removeSource(this.calendar_collection_key); - - Collections.get('calendars').removeListener(this); - Collections.get('calendars').removeSource(this.calendar_collection_key); - - if (CalendarService.preview) { - CalendarService.closePopups(); - } - - Languages.removeListener(this); - CalendarService.removeListener(this); - } - UNSAFE_componentWillUpdate(nextProps, nextState) { - if (nextProps.tab != null && nextProps.tab.configuration.calendars) { - this.allowed_ids = nextProps.tab.configuration.calendars.map(c => c.calendar_id); - } - - // eslint-disable-next-line no-unused-vars - var filter = nextState.filter; - if (!this.loaded_date_range['both']) { - this.loaded_date_range['both'] = {}; - } - var range = this.loaded_date_range['both']; - var requested_min = - moment(CalendarService.date).startOf('month').toDate().getTime() / 1000 - 60 * 60 * 24 * 31; - var requested_max = - moment(CalendarService.date).startOf('month').toDate().getTime() / 1000 + - 2 * 60 * 60 * 24 * 31; - if ( - !range.min || - range.min > requested_min || - range.max < requested_max || - new Date().getTime() - range.last_updated.getTime() > 60000 - ) { - this.setLoadedRange('both', requested_min, requested_max); - - var calendar_list = Collections.get('calendars') - .findBy({ workspace_id: RouterService.getStateFromRoute().workspaceId }) - .map(cal => { - return { - calendar_id: cal.id, - workspace_id: cal.workspace_id, - }; - }); - - if (this.props.tab != null) { - calendar_list = calendar_list.filter(c => this.allowed_ids.indexOf(c.calendar_id) >= 0); - } - - Collections.get('events').sourceLoad( - this.calendar_collection_key, - { - after_ts: requested_min, - before_ts: requested_max, - calendar_list: calendar_list, - mode: 'both', - }, - () => {}, - ); - } - } - componentDidUpdate() { - if (this.date !== CalendarService.date && this.calendar) { - this.calendar.setDate(CalendarService.date, true); - this.date = CalendarService.date; - } - } - configureCalendarConnector(app, cal) { - var data = { - calendar: cal, - }; - WorkspacesApps.notifyApp(app.id, 'configuration', 'calendar', data); - } - renderCalendarList() { - const { workspaceId } = RouterService.getStateFromRoute(); - var calendars = Collections.get('calendars').findBy({ - workspace_id: workspaceId, - }); - if (this.props.tab != null) { - calendars = calendars.filter(c => this.allowed_ids.indexOf(c.id) >= 0); - } - - if (calendars && calendars.length > 0) { - var list = []; - calendars.forEach(cal => { - var el = ( -
-
{cal.title} -
- ); - if (WorkspaceUserRights.hasWorkspacePrivilege()) { - list.push({ - type: 'menu', - text: el, - submenu: [ - { - type: 'menu', - text: Languages.t( - 'scenes.apps.calendar.modify_calendar_menu', - [], - 'Modifier le calendrier', - ), - submenu_replace: true, - submenu: [ - { - type: 'title', - text: Languages.t( - 'scenes.apps.calendar.edit_calendar_title', - [], - 'Éditer le calendrier', - ), - }, - { - type: 'react-element', - reactElement: level => ( - - ), - }, - ], - }, - { - type: 'menu', - className: 'error', - text: Languages.t('general.delete', [], 'Supprimer'), - onClick: () => { - AlertManager.confirm( - () => { - Collections.get('calendars').remove(cal, this.calendar_collection_key); - }, - () => {}, - { - text: Languages.t( - 'scenes.apps.calendar.remove_calendar_confirmation', - [], - 'Supprimer le calendrier et ses événements définitivement ?', - ), - }, - ); - }, - }, - { type: 'separator' }, - { - type: 'menu', - text: Languages.t('scenes.apps.calendar.connectors_menu', [], 'Connecteurs...'), - submenu: [ - { - type: 'react-element', - reactElement: level => { - var apps = getCompanyApplications(Groups.currentGroupId).filter(app => false); - if (apps.length > 0) { - return ( - getApplication(id)) - .filter(item => item)} - configurable={item => - ((item.display || {}).configuration || {}).can_configure_in_calendar - } - onChange={ids => { - cal.connectors = ids; - Collections.get('calendars').save(cal, this.calendar_collection_key); - }} - onConfig={app => { - this.configureCalendarConnector(app, cal); - }} - /> - ); - } - return ( -
- {Languages.t( - 'scenes.apps.calendar.no_connectors_menu_text', - [], - "Vous n'avez aucun connecteur capable de se connecter à un calendrier.", - )} -
- ); - }, - }, - { type: 'separator' }, - { - type: 'menu', - text: Languages.t( - 'scenes.apps.calendar.connectors_search_menu', - [], - 'Chercher des connecteurs...', - ), - onClick: () => { - popupManager.open( - , - true, - 'workspace_parameters', - ); - }, - }, - ], - }, - ], - }); - } else { - list.push({ type: 'menu', text: el }); - } - }); - return list; - } else { - return [ - { - type: 'text', - text: Languages.t( - 'scenes.apps.calendar.no_calendar_text', - [], - "Vous n'avez défini aucun calendrier pour cet espace de travail.", - ), - }, - ]; - } - } - export(download) { - if (!this.state.export_my_calendar && !this.state.export_workspace_calendar) { - return; - } - var mode = 'mine'; - var calendar_list = []; - if (this.state.export_my_calendar && this.state.export_workspace_calendar) { - mode = 'both'; - } else if (this.state.export_workspace_calendar) { - mode = 'workspace'; - } - - if (mode === 'both' || mode === 'workspace') { - calendar_list = Collections.get('calendars') - .findBy({ workspace_id: RouterService.getStateFromRoute().workspaceId }) - .map(cal => { - return { - calendar_id: cal.id, - workspace_id: cal.workspace_id, - }; - }); - if (this.props.tab != null) { - calendar_list = calendar_list.filter(c => this.allowed_ids.indexOf(c.calendar_id) >= 0); - } - } - - CalendarService.export( - WorkspacesService.currentWorkspaceId, - { mode: mode, calendar_list: calendar_list }, - download, - token => { - if (!download) { - AlertManager.alert(() => {}, { text: }); - } - }, - ); - } - - completeRect(rect) { - rect.x = rect.x || rect.left; - rect.y = rect.y || rect.top; - return rect; - } - render() { - const { workspaceId } = RouterService.getStateFromRoute(); - if ( - this.props.tab != null && - (!this.props.tab.configuration || this.props.tab.configuration.calendars === undefined) - ) { - return ( - - ); - } - - var calendars = Collections.get('calendars') - .findBy({ workspace_id: workspaceId }) - .map(cal => cal.id); - - if (this.props.tab != null) { - calendars = calendars.filter(c => this.allowed_ids.indexOf(c) >= 0); - } - - var events = Collections.get('events') - .findBy({}) - .filter(event => { - if (!event.id) { - return true; - } - event._user_transparent = false; - - var not_mine = - (event.participants || []).filter( - part => part.user_id_or_mail === UserService.getCurrentUserId(), - ).length === 0; - if (this.state.filter === 'mine') { - if (not_mine) { - event._user_transparent = true; - } - return true; - } - if (this.state.filter === 'workspace' || this.state.filter === 'custom') { - //Not in this workspace - if ( - event.workspaces_calendars.filter(part => calendars.indexOf(part.calendar_id) >= 0) - .length === 0 - ) { - if (!not_mine) { - //Set transparent event - event._user_transparent = true; - return true; - } else { - return false; - } - } - } - return true; - }); - - if ( - CalendarService.edited && - CalendarService.edited.from && - CalendarService.edited.from !== this.lastEditedFrom - ) { - this.lastEditedFrom = CalendarService.edited.from; - this.calendar.setDate(new Date(CalendarService.edited.from * 1000), true); - - if (!CalendarService.edited.id) { - setTimeout(() => { - var htmlEl = this.calendar.getDomElement(CalendarService.edited); - if (htmlEl) { - ModalManager.updateHighlight(this.completeRect(window.getBoundingClientRect(htmlEl))); - } - }, 200); - } - } - - var calendar_menu = [ - { - type: 'menu', - text: Languages.t('scenes.apps.calendar.my_calendar_menu', [], 'Mon calendrier'), - onClick: () => { - this.setState({ filter: 'mine' }); - CalendarService.filter_mode = 'mine'; - Menu.closeAll(); - }, - }, - { - type: 'menu', - text: Languages.t('scenes.apps.calendar.workspace_menu', [], 'Espace de travail'), - onClick: () => { - this.setState({ filter: 'workspace' }); - CalendarService.filter_mode = 'workspace'; - Menu.closeAll(); - }, - }, - /*{type:"menu", text:"Personnaliser...", onClick: ()=>{ - - }},*/ - { type: 'separator' }, - { - type: 'menu', - text: Languages.t('scenes.apps.calendar.export_view_menu', [], 'Exporter la vue'), - submenu: [ - { type: 'title', text: Languages.t('scenes.apps.calendar.export_title', [], 'Exporter') }, - { - type: 'react-element', - reactElement: () => { - return ( - { - this.setState({ - export_my_calendar: my, - export_workspace_calendar: ws, - }); - }} - /> - ); - }, - }, - { - type: 'menu', - text: Languages.t( - 'scenes.apps.calendar.ics_subscription_menu', - [], - "Obtenir un lien d'abonnement ICS", - ), - onClick: () => { - this.export(false); - }, - }, - { - type: 'menu', - text: Languages.t( - 'scenes.apps.calendar.ics_download_menu', - [], - 'Télécharger un fichier ICS', - ), - onClick: () => { - this.export(true); - }, - }, - ], - }, - { type: 'separator' }, - ]; - - calendar_menu = calendar_menu.concat(this.renderCalendarList()); - - if (this.props.tab === null && WorkspaceUserRights.hasWorkspacePrivilege()) { - calendar_menu = calendar_menu.concat([ - { - type: 'menu', - text: Languages.t( - 'scenes.apps.calendar.add_calendar_menu', - [], - 'Ajouter un calendrier...', - ), - submenu_replace: true, - submenu: [ - { - type: 'title', - text: Languages.t( - 'scenes.apps.calendar.create_calendar_title', - [], - 'Créer un calendrier', - ), - }, - { - type: 'react-element', - reactElement: level => { - return ( - - ); - }, - }, - ], - }, - ]); - } - - var list = []; - - list.push( -
-
-
- - { - { - mine: Languages.t('scenes.apps.calendar.my_calendar', [], 'Mon calendrier'), - workspace: Languages.t('scenes.apps.calendar.workspace', [], 'Espace de travail'), - }[this.state.filter] - } - -
- -
- {this.state.view !== 'dayGridMonth' && ( -
- {Languages.t('scenes.apps.calendar.calendar.week_btn', [], 'Semaine')}{' '} - {moment(CalendarService.date).week()} -
- )} - - this.calendar.today(), - }, - { type: 'separator' }, - { - type: 'react-element', - reactElement: () => ( -
- { - this.calendar.setDate(value); - Menu.closeAll(); - }} - /> -
- ), - }, - ]} - > - {CalendarService.date && - (this.state.view === 'dayGridMonth' || - this.state.view === 'timeGridWeek' || - this.state.view === 'listYear') && - moment(CalendarService.date).format('MMMM YYYY')} - {CalendarService.date && - this.state.view === 'timeGridDay' && - moment(CalendarService.date).format('LL')} -
- -
- this.calendar.previous()} - /> - this.calendar.next()} - /> -
- -
- { - this.change('title', evt.target.value); - }} - className="full_width bottom-margin" - big - /> - -
- { - this.change('title', evt.target.value); - }} - className="full_width bottom-margin" - big - /> - -
- - - - ); - } -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/Attachments.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/Attachments.tsx deleted file mode 100644 index af14764f..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/Attachments.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Card, Col, Row, Tooltip, Typography } from 'antd'; -import { FileType } from 'app/features/files/types/file'; -import '../input.scss'; -import React from 'react'; - -type PropsType = { - files: FileType[]; -}; - -const { Text } = Typography; -export default ({ files }: PropsType) => { - return ( - - {files.map(f => ( - - - - - {f?.metadata?.name || ''} - - - - - ))} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/EphemeralMessages.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/EphemeralMessages.tsx deleted file mode 100644 index 64f909dd..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/EphemeralMessages.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import Languages from 'app/features/global/services/languages-service'; -import { useEphemeralMessages } from 'app/features/messages/hooks/use-ephemeral-messages'; -import useRouterCompany from 'app/features/router/hooks/use-router-company'; -import { useEffect } from 'react'; -import { MessageContext } from '../../message/message-with-replies'; -import MessageContent from '../../message/parts/MessageContent'; -import ThreadSection from '../../parts/thread-section'; - -type Props = { - channelId: string; - workspaceId: string; - threadId: string; - onHasEphemeralMessage: () => void; - onNotEphemeralMessage: () => void; -}; - -export default (props: Props) => { - const companyId = useRouterCompany(); - const workspaceId = props.workspaceId; - const channelId = props.channelId; - const { lastEphemeral } = useEphemeralMessages({ - companyId, - channelId: props.channelId, - }); - - const messageKey = { - id: lastEphemeral?.id || '', - threadId: lastEphemeral?.thread_id || '', - companyId, - }; - - const hasEphemeral = - lastEphemeral && - (lastEphemeral.thread_id === props.threadId || - (!props.threadId && - (!lastEphemeral.thread_id || lastEphemeral.thread_id === lastEphemeral.id))); - - useEffect(() => { - if (lastEphemeral && hasEphemeral) { - props.onHasEphemeralMessage(); - } else { - props.onNotEphemeralMessage(); - } - }, [lastEphemeral, hasEphemeral]); - - if (!hasEphemeral) { - return
; - } - - const updatedKey = - lastEphemeral.id + lastEphemeral.ephemeral?.version + lastEphemeral.ephemeral?.id; - - return ( -
-
- {Languages.t('scenes.apps.messages.just_you', [], 'Visible uniquement par vous')} -
- - - - - - -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/InputOptions.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/InputOptions.tsx deleted file mode 100644 index b12b2706..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/InputOptions.tsx +++ /dev/null @@ -1,276 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import React, { useState } from 'react'; -import { EditorState } from 'draft-js'; -import { Smile, Video, MoreHorizontal, Paperclip, Type } from 'react-feather'; -import { Button, Tooltip } from 'antd'; -import EmojiPicker from 'components/emoji-picker/emoji-picker'; -import Menu from 'components/menus/menu'; -import MenusManager from 'app/components/menus/menus-manager'; -import Languages from 'app/features/global/services/languages-service'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps'; -import MessageEditorsManager from 'app/features/messages/services/message-editor-service-factory'; -import EditorToolbar from 'app/components/rich-text-editor/editor-toolbar'; -import { Application } from 'app/features/applications/types/application'; -import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; - -type Props = { - channelId: string; - threadId: string; - onAddEmoji?: (emoji: any) => void; - onSend?: () => void; - triggerApp?: (app: any, fromIcon: any, evt: any) => void; - isEmpty: boolean; - onRichTextChange: (editorState: EditorState) => void; - richTextEditorState: EditorState; -}; - -type MenuItem = { - type: string; - emoji?: any; - icon?: any; - text?: string; - onClick?: (event: Event) => void; -}; - -export default (props: Props) => { - const [displayRichTextOptions, setDisplayRichTextOptions] = useState(false); - const [, setDisplayFileMenu] = useState(false); - const [, setDisplayEmojiMenu] = useState(false); - const addon_menu: MenuItem[] = []; - const addon_right_icon: any[] = []; - const addon_files: any[] = []; - const addon_calls: any[] = []; - - const apps = useCompanyApplications().applications.filter( - (app: Application) => app.display?.twake?.chat?.input, - ); - - if (props.triggerApp) { - if (apps.length > 0) { - // eslint-disable-next-line array-callback-return - apps.map((app: Application) => { - if (app) { - let icon = WorkspacesApps.getAppIcon(app); - let emoji = ''; - if ((icon || '').indexOf('http') === 0) { - emoji = icon; - icon = ''; - } - const menu_item: MenuItem = { - type: 'menu', - emoji, - icon, - text: app.identity?.name, - onClick: (evt: unknown) => { - props.triggerApp && props.triggerApp(app, undefined, evt); - }, - }; - - if ( - app.display?.twake?.chat?.input?.type === 'file' && - app?.identity?.code !== 'twake_drive' - ) { - addon_files.push(menu_item); - } else if ( - app?.identity?.code === 'jitsi' || - app.display?.twake?.chat?.input?.type === 'call' - ) { - addon_calls.push(menu_item); - } else if (app?.identity?.code !== 'twake_drive') { - addon_menu.push(menu_item); - } - } - }); - } - } - - const RichTextToolbar = () => ( - props.onRichTextChange(editorState)} - /> - ); - - const displayToolbar = () => { - return displayRichTextOptions; - }; - - return ( -
-
- {addon_files.length > 0 && ( - setDisplayFileMenu(true)} - onClose={() => setDisplayFileMenu(false)} - menu={[ - { - type: 'menu', - icon: 'desktop', - text: Languages.t('scenes.apps.messages.input.attach_file.from_computer'), - onClick: () => { - MessageEditorsManager.get(props.channelId).openFileSelector(props.threadId); - }, - }, - ...addon_files, - ]} - > - - - - - )} - - {addon_files.length === 0 && ( - - - - )} - - {props.onAddEmoji && ( - setDisplayEmojiMenu(true)} - onClose={() => setDisplayEmojiMenu(false)} - menu={[ - { - type: 'react-element', - className: 'menu-cancel-margin', - reactElement: () => { - return ( - { - MenusManager.closeMenu(); - props.onAddEmoji && props.onAddEmoji(emoji); - }} - /> - ); - }, - }, - ]} - > - - - - - )} - - {addon_calls.length > 1 && ( - - - - - - )} - - {addon_calls.length === 1 && ( - - - - )} - - - - - - {addon_right_icon.map((app: Application) => { - return ( - - ); - })} - - {addon_menu.length > 0 && ( - - - - )} -
- - {displayToolbar() && ( -
-
- -
-
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/IsWriting.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/IsWriting.tsx deleted file mode 100644 index 58dfe214..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/IsWriting.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -import { useChannelWritingActivityState } from 'app/features/channels/hooks/use-channel-writing-activity'; -import Languages from 'app/features/global/services/languages-service'; -import WritingLoader from 'app/components/writing-loader/writing-loader'; -import { ChannelWritingActivityType } from 'app/features/channels/state/channel-writing-activity'; - -type PropsType = { - channelId: string; - threadId: string; -}; - -const getChannelActivityMessage = (channelActivityUsers: ChannelWritingActivityType[]) => { - const channelActivityUsersCount = channelActivityUsers.length; - let channelActivityMessage = ''; - - if (channelActivityUsersCount === 1) { - channelActivityMessage = Languages.t( - 'scenes.app.messages.input.parts.is_writing.message.user_is_writing', - [channelActivityUsers[0].name], - ); - } - - if (channelActivityUsersCount === 2) { - channelActivityMessage = Languages.t( - 'scenes.app.messages.input.parts.is_writing.message.users_are_writing', - [channelActivityUsers[0].name, channelActivityUsers[1].name], - ); - } - - if (channelActivityUsersCount > 2) { - channelActivityMessage = Languages.t( - 'scenes.app.messages.input.parts.is_writing.message.users_and_more_are_writing', - [channelActivityUsers[0].name, channelActivityUsers[1].name, channelActivityUsersCount - 2], - ); - } - - return channelActivityMessage; -}; - -export default ({ channelId, threadId }: PropsType): JSX.Element => { - const channelActivityUsers = useChannelWritingActivityState(channelId, threadId); - const [writtingInfo, setWritingInfo] = useState(''); - - useEffect(() => { - const message = getChannelActivityMessage(channelActivityUsers); - - setWritingInfo(message); - }, [channelActivityUsers]); - - return writtingInfo.length > 0 ? ( -
-
-
{writtingInfo}
-
-
- ) : ( - <> - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/MessageExternalFilePicker.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/MessageExternalFilePicker.tsx deleted file mode 100644 index d0f809a7..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/MessageExternalFilePicker.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { ReactNode, useEffect } from 'react'; -import { MessageFileType } from 'app/features/messages/types/message'; -import { useUploadZones } from 'app/features/files/hooks/use-upload-zones'; -import { ChannelType } from 'app/features/channels/types/channel'; -import { useMessageEditor } from 'app/features/messages/hooks/use-message-editor'; - -export default (props: { - children: ReactNode; - setHandler: (handler: (file: MessageFileType) => void) => void; - channel: ChannelType; - threadId?: string; -}) => { - const { key: editorId } = useMessageEditor({ - companyId: props.channel.company_id || '', - workspaceId: props.channel.workspace_id || '', - channelId: props.channel.id, - threadId: props.threadId, - }); - const { files, setFiles } = useUploadZones(editorId); - - useEffect(() => { - props.setHandler((file: MessageFileType) => { - setFiles([...files, file]); - }); - }, [props.setHandler]); - - return <>{props.children}; -}; diff --git a/twake/frontend/src/app/views/applications/messages/input/parts/PendingAttachments.tsx b/twake/frontend/src/app/views/applications/messages/input/parts/PendingAttachments.tsx deleted file mode 100644 index cd2a6d49..00000000 --- a/twake/frontend/src/app/views/applications/messages/input/parts/PendingAttachments.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useEffect } from 'react'; -import { Col, Row } from 'antd'; -import { useUploadZones } from 'app/features/files/hooks/use-upload-zones'; -import '../input.scss'; -import { MessageFileType } from 'app/features/messages/types/message'; -import _ from 'lodash'; -import PossiblyPendingAttachment from '../../message/parts/PossiblyPendingAttachment'; - -type PropsType = { - zoneId: string; - initialValue?: MessageFileType[]; - onChange?: (list: MessageFileType[]) => void; -}; - -export default ({ zoneId, onChange, initialValue }: PropsType) => { - const { files, setFiles } = useUploadZones(zoneId); - - useEffect(() => { - if (initialValue) setFiles(initialValue.filter(f => f?.metadata?.source !== 'pending')); - }, []); - - useEffect(() => { - if (onChange) onChange(files); - }, [files]); - - return files.length > 0 ? ( - - {files.map((file, index) => { - return ( - - - setFiles( - files.filter( - f => !_.isEqual(f.metadata?.external_id, file.metadata?.external_id), - ), - ) - } - /> - - ); - })} - - ) : ( - <> - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/list-builder.tsx b/twake/frontend/src/app/views/applications/messages/list-builder.tsx deleted file mode 100644 index 1e138d95..00000000 --- a/twake/frontend/src/app/views/applications/messages/list-builder.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable react/prop-types */ -import React, { - forwardRef, - ReactNode, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react'; -import { ItemContent, Virtuoso, VirtuosoHandle } from 'react-virtuoso'; -import _ from 'lodash'; - -export type ListBuilderHandle = VirtuosoHandle & unknown; - -type Props = { - items: any[]; - followOutput: false | 'smooth' | 'auto'; - loadMore: (direction: 'history' | 'future', limit: number, offset?: any) => Promise; - itemContent: ItemContent; - itemId: (item: any) => string; - emptyListComponent: ReactNode; - onScroll: (e: React.UIEvent<'div', UIEvent>) => void; - style?: React.CSSProperties; - atBottomStateChange?: (atBottom: boolean) => void; - //Will be called just before to finish append messages for a final filtering - filterOnAppend?: (item: any[]) => any[]; -}; - -let prependMoreLock = false; -let appendMoreLock = false; - -export default React.memo( - forwardRef( - ( - { - emptyListComponent, - filterOnAppend, - followOutput, - itemId, - loadMore, - onScroll, - items: _items, - itemContent, - atBottomStateChange, - style, - }: Props, - ref, - ) => { - const START_INDEX = 10000000; - const INITIAL_ITEM_COUNT = (_items || []).length; - - const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX); - const [items, setItems] = useState(_items || []); - const refVirtuoso = useRef(null); - - useImperativeHandle(ref, () => ({ - ...refVirtuoso.current, - })); - - useEffect(() => { - // Detect append or prepend or full replace items - const ids = items.map(i => itemId(i)); - //Find the first index in props _items that is already in displayed items - const first = _items.findIndex(i => ids.includes(itemId(i))); - //Find the last index in props _items that is already in displayed items - const lastIndex = _items - .slice() - .reverse() - .findIndex(i => ids.includes(itemId(i))); - const last = lastIndex >= 0 ? _items.length - 1 - lastIndex : lastIndex; - if (first == -1) { - //Replacement - setFirstItemIndex(START_INDEX); - setItems(_items); - } else if (first === 0 && last !== _items.length - 1) { - //Append - let newList = [...items, ..._items.slice(last + 1)]; - if (filterOnAppend) newList = filterOnAppend(newList); - setItems(newList); - } else if (last === _items.length - 1 && first !== 0) { - //Prepend - const newItems = _items.slice(0, first); - const nextFirstItemIndex = firstItemIndex - newItems.length; - setFirstItemIndex(() => nextFirstItemIndex); - setItems([...newItems, ...items]); - } else { - if (filterOnAppend) { - const newList = filterOnAppend([...items, ..._items]); - if ( - _.difference( - items.map(i => itemId(i)), - newList.map(i => itemId(i)), - ).length > 0 - ) { - setItems(newList); - } - } - } - }, [_items]); - - const appendItems = useCallback(() => { - if (appendMoreLock) return; - appendMoreLock = true; - - setTimeout(async () => { - await loadMore('future', 20, items[items.length - 1]); - appendMoreLock = false; - }, 10); - - return false; - }, [items]); - - const prependItems = useCallback(() => { - if (prependMoreLock) return; - prependMoreLock = true; - - setTimeout(async () => { - await loadMore('history', 20, items[0]); - prependMoreLock = false; - }, 10); - - return false; - }, [items]); - - if (items.length === 0) { - return
{emptyListComponent}
; - } - - return ( - onScroll(e)} - atBottomStateChange={atBottomStateChange} - computeItemKey={(_index, item) => itemId(item)} - /> - ); - }, - ), -); diff --git a/twake/frontend/src/app/views/applications/messages/message/message-with-replies.tsx b/twake/frontend/src/app/views/applications/messages/message/message-with-replies.tsx deleted file mode 100644 index bc0c2c2e..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/message-with-replies.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useContext, useState } from 'react'; -import Message from './message'; -import Responses from './responses'; -import ReplyBlock from './parts/ReplyBlock'; -import LoadMoreReplies from './parts/LoadMoreReplies'; -import { MessagesListContext } from '../messages-list'; -import ThreadSection from '../parts/thread-section'; -import Thread from '../parts/thread'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import ActivityMessage, { ActivityType } from './parts/ChannelActivity/ActivityMessage'; -import { useHighlightMessage } from 'app/features/messages/hooks/use-highlight-message'; -import { NodeMessage } from 'app/features/messages/types/message'; - -export const MessageContext = React.createContext({ - companyId: '', - workspaceId: '', - channelId: '', - threadId: '', - id: '', -}); - -type Props = { - companyId: string; - workspaceId: string; - channelId: string; - threadId: string; - id?: string; -}; - -export default React.memo(({ threadId, workspaceId, channelId, companyId, id }: Props) => { - return ( - - - - ); -}); - -const MessageType = () => { - const listContext = useContext(MessagesListContext); - const context = useContext(MessageContext); - const { message } = useMessage(context); - const { highlight } = useHighlightMessage(); - const highlighted = - (highlight && - ((highlight.threadId === context.id && !listContext.hideReplies) || - highlight.answerId === context.id)) || - false; - const [firstMessageId, setFirstMessageId] = useState( - message.last_replies?.[0]?.id || message.thread_id, - ); - - if (message.subtype === 'system') { - const activity = message?.context?.activity as ActivityType; - return ; - } - - return ( - - - {!listContext.hideReplies && ( - <> - { - if (firstMessage) setFirstMessageId(firstMessage.id); - }} - /> - - {!listContext.readonly && } - - )} - - ); -}; - -const HeadMessage = () => { - const context = useContext(MessageContext); - const { message } = useMessage(context); - return ( - - - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/message.scss b/twake/frontend/src/app/views/applications/messages/message/message.scss deleted file mode 100644 index 1dc038d3..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/message.scss +++ /dev/null @@ -1,245 +0,0 @@ -.message { - text-align: left; - .deleted-message { - opacity: 0.5; - } - .message_is_loading { - opacity: 0.5; - } - .message-not-sent.content { - opacity: 0.5; - } - .alert_failed_icon { - margin-left: 8px; - margin-top: 0px; - display: flex; - color: var(--red); - } - .sender-space { - width: 28px; - margin-right: 8px; - display: flex; - justify-content: flex-end; - .sender-head, - .app_icon { - width: 28px; - height: 28px; - border-radius: 50%; - background-color: var(--grey-background); - background-size: cover; - background-position: center; - - .online_user_status { - position: relative; - bottom: -20px; - right: -20px; - } - } - - .sender-head { - .user_online_stPPatus { - right: -20px; - } - } - } - .message-content { - flex: 1; - min-width: 0; - width: 0; - - &.link-to-thread { - color: var(--grey-dark); - .content-parent { - &:before { - content: ''; - position: absolute; - left: 0px; - height: 100%; - width: 3px; - border-radius: 2px; - background: var(--grey-light); - } - position: relative; - padding-left: 8px; - } - .sender-status { - display: none; - } - } - .message-content-header-container { - position: relative; - display: inline-block; - overflow: hidden; - white-space: nowrap; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - .message-content-header { - position: relative; - display: inline-block; - overflow: hidden; - white-space: nowrap; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: baseline; - - .sender-name { - font-weight: 700; - margin-right: 4px; - cursor: pointer; - } - .sender-status { - font-size: 12px; - color: var(--grey-dark); - margin-right: 4px; - } - .reply-text { - margin-right: 4px; - a, - a * { - display: inline; - } - overflow: hidden; - text-overflow: ellipsis; - } - .date { - color: var(--grey-dark); - font-size: 12px; - &:hover { - text-decoration: underline; - } - } - } - .message_header_loader { - height: 16px; - width: 16px; - margin-left: 4px; - } - } - } - - .message-options { - position: absolute; - background-color: var(--white); - height: 32px; - border-radius: 8px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); - border: 1px solid var(--grey-light); - display: block; - top: -16px; - margin: auto; - right: 8px; - z-index: 1; - opacity: 0; - display: flex; - flex-direction: row; - overflow: hidden; - - &.drag { - left: -8px; - right: unset; - display: none; - - .option, - .option:hover { - min-width: 28px; - color: var(--primary); - cursor: grab; - } - } - - .separator { - height: 100%; - border-right: 1px solid var(--grey-light); - } - - .option { - min-width: 24px; - display: flex; - align-items: center; - justify-content: center; - color: var(--grey-dark); - cursor: pointer; - margin: 4px; - border-radius: 4px; - - &:hover { - color: var(--black); - background-color: var(--primary-background); - } - - &.active { - border: 1px solid var(--primary); - background-color: var(--primary-background); - } - } - } - .message-content.active { - .message-options.right { - opacity: 1; - } - } - &:hover { - .message-options { - opacity: 1; - } - } - - .message-content.loading-interaction { - opacity: 0.5; - pointer-events: none; - } - - .reactions { - padding-top: 8px; - - .reaction_container { - width: auto; - display: inline-block; - } - - .reaction { - display: inline-flex; - justify-content: center; - align-items: center; - margin-right: 4px; - border-radius: var(--border-radius-base); - box-sizing: border-box; - color: var(--black); - height: 24px; - min-width: 36px; - border: 0; - padding: 3px 4px; - font-size: 14px; - line-height: 16px; - border: 1px solid var(--grey-background); - font-weight: 500; - cursor: pointer; - - .emoji-container { - margin-right: 4px; - line-height: 0px; - } - - &.is_selected { - border: 1px solid var(--primary); - color: var(--primary); - background: var(--white); - } - - &:hover { - background: var(--primary-background); - } - } - } -} - -body:not(.drag_message) { - .draggable { - .message-options.drag { - display: flex; - } - } -} diff --git a/twake/frontend/src/app/views/applications/messages/message/message.tsx b/twake/frontend/src/app/views/applications/messages/message/message.tsx deleted file mode 100644 index 25895bd0..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/message.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import MessageContent from './parts/MessageContent'; - -export default () => { - return ; -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.tsx deleted file mode 100644 index e184e373..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import Twacode from 'app/components/twacode/twacode'; -import React, { ReactNode, Suspense } from 'react'; -import Markdown from 'markdown-to-jsx'; -import HighlightedCode from 'app/components/highlighted-code/highlighted-code'; -import { preparse, preunparse } from './Blocks.utils'; -import User from 'components/twacode/blocks/user'; -import Chan from 'components/twacode/blocks/chan'; -import { blocksToTwacode, formatData } from 'app/components/twacode/blocksCompiler'; -import environment from 'app/environment/environment'; -import { Block } from 'app/components/twacode/types'; - -type Props = { - blocks: unknown[]; - fallback: string | ReactNode; - onAction: (type: string, id: string, context: unknown, passives: unknown, evt: unknown) => void; - allowAdvancedBlocks?: boolean; -}; - -const Code = ({ className, children }: { className: string; children: string }) => { - children = preunparse(children); - if (children.split('\n').length === 1) { - return {children}; - } - return ; -}; - -const Link = ({ href, children }: { href: string; children: string }) => { - let target = '_blank'; - if (!href) { - return {children}; - } - //If same domain, stay on the same tab - if ( - href - ?.replace(/https?:\/\//g, '') - ?.split('/')[0] - ?.toLocaleLowerCase() === - environment.front_root_url - .replace(/https?:\/\//g, '') - .split('/')[0] - ?.toLocaleLowerCase() - ) { - target = '_self'; - } - - return ( - - {children} - - ); -}; - -export default React.memo((props: Props) => { - const flattedBlocks: Block[] = []; - formatData(props.blocks || [], 'content', flattedBlocks); - const blocks = blocksToTwacode(flattedBlocks); - - if (!props.blocks?.length || !props.allowAdvancedBlocks) { - return typeof props.fallback === 'string' ? ( -
- ( - <> - {' '} - - ), - }, - channel: { - component: ({ id }) => ( - <> - {' '} - - ), - }, - h1: ({ children }) => children, - h2: ({ children }) => children, - h3: ({ children }) => children, - h4: ({ children }) => children, - h5: ({ children }) => children, - h6: ({ children }) => children, - }, - }} - > - {preparse(props.fallback || '')} - -
- ) : ( - <>{props.fallback} - ); - } - - return ( - }> - - props.onAction(type, id, context, passives, evt) - } - /> - - ); -}); diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.utils.ts b/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.utils.ts deleted file mode 100644 index 714d019e..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/Blocks.utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const preparse = (str: string) => { - const ret = str - //Fix markdown simple line break - .replace(/\n/g, ' \n') - .replace(/^(([0-9]+\.)|[>`\-*].*\n) *$\n/gm, '$1\n\n') - //Prevent html security issues - .replace(/&/g, '&') - .replace(/') - .replace(/([ \t]|^)#([\w]+)/g, '$1') - //Prepare code - .replace(/[^\n]\r?\n{1}`{3}/, '\n\n```'); - return ret; -}; - -export const preunparse = (str: string) => { - return ( - str - //Prepare mentions - .replace(/([ \t]|^)/g, '$1@$2') - .replace(/([ \t]|^)/g, '$1#$2') - //Prevent html security issues - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/ChannelActivity/ActivityMessage.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/ChannelActivity/ActivityMessage.tsx deleted file mode 100644 index 857ad575..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/ChannelActivity/ActivityMessage.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import React from 'react'; -import { Row, Typography } from 'antd'; -import Languages from 'app/features/global/services/languages-service'; -import Emojione from 'app/components/emojione/emojione'; -import User from 'app/components/twacode/blocks/user'; -import { ChannelType } from 'app/features/channels/types/channel'; -import { TabType } from 'app/features/tabs/types/tab'; -import { getCompanyApplications } from 'app/features/applications/state/company-applications'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import { ChannelMemberType } from 'app/features/channel-members/types/channel-member-types'; -import { MessageWithReplies } from 'app/features/messages/types/message'; -import { Application } from 'app/features/applications/types/application'; - -enum ChannelActivityEnum { - CHANNEL_MEMBER_CREATED = 'channel:activity:member:created', - CHANNEL_MEMBER_DELETED = 'channel:activity:member:deleted', - CHANNEL_UPDATED = 'channel:activity:updated', - CHANNEL_TAB_CREATED = 'channel:activity:tab:created', - CHANNEL_TAB_DELETED = 'channel:activity:tab:deleted', - CHANNEL_CONNECTOR_CREATED = 'channel:activity:connector:created', - CHANNEL_CONNECTOR_DELETED = 'channel:activity:connector:deleted', -} - -export type ActivityType = { - type: ChannelActivityEnum; - actor: { - type: 'user'; - id: string; - }; - context: { - type: 'add' | 'diff' | 'remove'; - array?: { - type: string; - resource: ChannelMemberType | TabType; - }[]; - previous?: { - type: string; - // should be a real type instead - resource: { id: string; name: string; description: string; icon: string }; - }; - next?: { - type: string; - // should be a real type instead - resource: { id: string; name: string; description: string; icon: string }; - }; - }; -}; - -type PropsType = { - refDom?: React.Ref; - activity: ActivityType; - message: MessageWithReplies; -}; - -// i18n but with react nodes as replacements -// TODO: maybe there is betters ways to do it with lodash -const translateUsingReactNode = (key: string, replacements: JSX.Element[]): JSX.Element[] => { - let temp = - Languages.t( - key, - replacements.map((_, i) => `{${i}}`), - ) || ''; - const list: JSX.Element[] = []; - replacements.forEach((replacement, i) => { - const split = temp.split(`{${i}}`); - list.push( - - {split[0]} - , - ); - list.push(replacement); - temp = split[1]; - }); - list.push( - - {temp} - , - ); - return list; -}; - -export default (props: PropsType): JSX.Element => { - const generateTypographyName = (id: string) => ; - const message = props.message; - - const memberJoinedOrInvited = (activity: ActivityType) => { - if (activity.context.array) { - const resource = activity.context.array[0]?.resource as ChannelMemberType; - - if (activity.actor.id === resource.user_id) { - return [<>]; //Do not show this information to not polute the chat - } - - if (activity.actor.id !== resource.user_id) { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_added_b_to_the_channel', - [ - - {generateTypographyName(activity.actor.id)} - , - - {generateTypographyName(resource.user_id || '')} - , - ], - ); - } - } - }; - - const memberLeftOrRemoved = (activity: ActivityType) => { - if (activity.context.array) { - const resource = activity.context.array[0]?.resource as ChannelMemberType; - if (activity.actor.id === resource.user_id) { - return []; //Do not show this information to not polute the chat - } - - if (activity.actor.id !== resource.user_id) { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_removed_b_from_the_channel', - [ - - {generateTypographyName(activity.actor.id)} - , - - {generateTypographyName(resource.user_id || '')} - , - ], - ); - } - } - }; - - const channelNameOrDescription = (activity: ActivityType) => { - const previous = activity.context.previous; - const next = activity.context.next; - - if ( - previous?.resource?.name !== next?.resource?.name || - previous?.resource?.icon !== next?.resource?.icon - ) { - const icon = ; - - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_updated_channel_name', - [ - - {generateTypographyName(activity.actor.id)} - , - - {icon} {next?.resource?.name} - , - ], - ); - } - - if (previous?.resource.description !== next?.resource.description) { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_updated_channel_description', - [ - - {generateTypographyName(activity.actor.id)} - , - ], - ); - } - }; - - const channelTabCreatedOrDeleted = (activity: ActivityType) => { - if (activity.context.array) { - const resource = activity.context.array[0].resource as TabType; - const connector = getCompanyApplications(Groups.currentGroupId).filter( - (app: Application) => app.id === resource.application_id, - ); - - if (activity.context.type === 'add') { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_created_channel_tab', - [ - - {generateTypographyName(activity.actor.id)} - , - - {connector[0]?.identity?.name} - , - - {resource?.name} - , - ], - ); - } - - if (activity.context.type === 'remove') { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_deleted_channel_tab', - [ - - {generateTypographyName(activity.actor.id)} - , - - {connector[0]?.identity?.name} - , - - {resource?.name} - , - ], - ); - } - } - }; - - const channelConnectorCreatedOrDeleted = (activity: ActivityType) => { - if (activity.context.array) { - const resource = activity.context.array[0].resource as ChannelType; - const connector = getCompanyApplications(Groups.currentGroupId).filter((app: Application) => - resource.connectors?.includes(app.id), - ); - - if (connector.length) { - if (activity.context.type === 'add') { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_created_channel_connector', - [ - - {generateTypographyName(activity.actor.id)} - , - - {connector[0]?.identity?.name} - , - ], - ); - } - - if (activity.context.type === 'remove') { - return translateUsingReactNode( - 'scenes.apps.messages.message.activity_message.a_deleted_channel_connector', - [ - - {generateTypographyName(activity.actor.id)} - , - - {connector[0]?.identity?.name} - , - ], - ); - } - } - } - }; - - const compute = (): string | JSX.Element[] => { - const process = new Map< - ChannelActivityEnum, - (activity: ActivityType) => JSX.Element[] | undefined - >(); - process - .set(ChannelActivityEnum.CHANNEL_MEMBER_CREATED, memberJoinedOrInvited) - .set(ChannelActivityEnum.CHANNEL_MEMBER_DELETED, memberLeftOrRemoved) - .set(ChannelActivityEnum.CHANNEL_UPDATED, channelNameOrDescription) - .set(ChannelActivityEnum.CHANNEL_TAB_CREATED, channelTabCreatedOrDeleted) - .set(ChannelActivityEnum.CHANNEL_TAB_DELETED, channelTabCreatedOrDeleted) - .set(ChannelActivityEnum.CHANNEL_CONNECTOR_CREATED, channelConnectorCreatedOrDeleted) - .set(ChannelActivityEnum.CHANNEL_CONNECTOR_DELETED, channelConnectorCreatedOrDeleted); - - const method = process.get(props.activity?.type) || (() => undefined); - return method(props.activity) || ''; - }; - - return ( -
- - {compute()} - -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/DeletedContent.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/DeletedContent.tsx deleted file mode 100644 index f49e1c40..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/DeletedContent.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import UsersService from 'app/features/users/services/current-user-service'; -import userAsyncGet from 'app/features/users/utils/async-get'; -import MenuManager from 'app/components/menus/menus-manager'; -import UserCard from 'app/components/user-card/user-card'; -import Languages from 'app/features/global/services/languages-service'; - -import '../message.scss'; -import { Typography } from 'antd'; -import { useDirectChannels } from 'app/features/channels/hooks/use-direct-channels'; -type PropsType = { - userId: string; -}; - -export default ({ userId }: PropsType) => { - const [fullName, setFullName] = useState(''); - const { openDiscussion } = useDirectChannels(); - - const _setFullName = async () => { - const user = await userAsyncGet(userId); - if (user) setFullName(UsersService.getFullName(user)); - else setFullName('not found'); - }; - useEffect(() => { - _setFullName(); - }); - - let user_name_node: HTMLSpanElement | null = null; - const displayUserCard = async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const box = (window as any).getBoundingClientRect(user_name_node); - const user = await userAsyncGet(userId); - - MenuManager.openMenu( - [ - user && { - type: 'react-element', - reactElement: () => ( - openDiscussion([user.id || ''])} /> - ), - }, - ], - box, - null, - { margin: 8 }, - ); - }; - - const translateUsingReactNode = (key: string, replacements: JSX.Element[]): JSX.Element[] => { - let temp = Languages.t( - key, - replacements.map((_, i) => `{${i}}`), - ); - const list: JSX.Element[] = []; - replacements.forEach((replacement, i) => { - const split = temp.split(`{${i}}`); - list.push( - - {split[0]} - , - ); - list.push(replacement); - temp = split[1]; - }); - list.push({temp}); - return list; - }; - - const clickableFullName = ( - (user_name_node = node)} - style={{ - fontWeight: 700, - marginRight: 4, - cursor: ' pointer', - }} - onClick={() => displayUserCard()} - > - {fullName} - - ); - - const isCurrentUser = UsersService.getCurrentUserId() === userId; - return isCurrentUser ? ( - <>{Languages.t('scenes.apps.messages.message.parts.deleted_content.text.current_user')} - ) : ( - <> - {translateUsingReactNode('scenes.apps.messages.message.parts.deleted_content.text', [ - clickableFullName, - ])} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.scss b/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.scss deleted file mode 100644 index 4bd5d889..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.scss +++ /dev/null @@ -1,44 +0,0 @@ -.first_message { - width: 100%; - text-align: center; - padding-bottom: 10vh; - padding-top: 10vh; - - .emojione { - width: 88px; - height: 88px; - padding-bottom: 10px; - } - .icon { - display: inline-block; - font-size: 88px; - } - - .channel_first_message_icon { - .ant-avatar-group .ant-avatar:not(:first-child) { - margin-left: -32px !important; - } - - .user_image { - width: 78px; - height: 78px; - background-size: cover; - background-position: center; - background-color: var(--white); - border-radius: 50%; - display: inline-block; - padding-bottom: 20px; - border: 2px solid #fff; - margin-left: -40px; - - &:first-child { - margin-left: 0px; - } - } - } - - .text { - padding-top: 10px; - opacity: 0.5; - } -} diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.tsx deleted file mode 100755 index 847c59f6..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstMessage.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; - -import Emojione from 'components/emojione/emojione'; -import './FirstMessage.scss'; - -export default React.memo(() => { - return ( -
-
-
- -
-
- {Languages.t('scenes.apps.messages.message.types.first_channel_message_text')} -
-
-
- ); -}); diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstThreadMessage.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstThreadMessage.tsx deleted file mode 100755 index 89621750..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/FirstMessage/FirstThreadMessage.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import './FirstMessage.scss'; - -export default ({ noReplies }: { noReplies?: boolean }) => { - return ( -
-
-
- {noReplies - ? Languages.t('scenes.apps.messages.message.types.no_message_in_thread') - : Languages.t('scenes.apps.messages.message.types.first_message_text')} -
-
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.scss b/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.scss deleted file mode 100644 index 14bf3ab4..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.scss +++ /dev/null @@ -1,70 +0,0 @@ -.link-preview { - border-radius: 0px !important; - border: 4px solid var(--primary) !important; - border-width: 0 0 0 4px !important; - margin-top: 15px !important; - padding-left: 15px !important; - - .delete-link-preview { - display: none; - position: absolute; - left: -32px; - top: -8px; - color: var(--grey) !important; - opacity: 0.5; - cursor: pointer; - padding: 8px; - - &:hover { - opacity: 1; - } - } - &:hover .delete-link-preview { - display: block; - } - - &.ant-card .ant-card-body { - padding: 0; - } - - &.ant-card .ant-card-meta-avatar { - padding-right: 0px; - width: 100%; - margin-bottom: 0px; - - span > img { - width: 16px; - height: 16px; - display: inline-block; - vertical-align: text-top; - } - - .ant-avatar { - text-align: start; - width: 24px; - } - } - - .link-preview-domain { - font-weight: bold; - } - - .preview-title > a { - font-size: 14px; - font-weight: bold; - float: left; - } - - .ant-card-meta-description { - margin-bottom: 10px; - color: var(--grey); - } - - .ant-card-cover { - cursor: pointer; - - img { - border-radius: 8px 8px 8px 8px; - } - } -} diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.tsx deleted file mode 100644 index 96041d95..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/LinkPreview.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useContext } from 'react'; -import { MessageLinkType } from 'app/features/messages/types/message'; -import './LinkPreview.scss'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import User from 'app/features/users/services/current-user-service'; -import { MessageContext } from '../message-with-replies'; -import { X } from 'react-feather'; - -type PropsType = { - preview: MessageLinkType; -}; - -export default ({ preview }: PropsType): React.ReactElement => { - const context = useContext(MessageContext); - const { deleteLinkPreview, message } = useMessage(context); - - return ( -
- {message.user_id === User.getCurrentUserId() ? ( -
- deleteLinkPreview(preview.url)} /> -
- ) : null} -
-
-
-
- {preview.favicon && ( - - {preview.domain} - - )} - {preview.domain} -
- -
{preview.description}
-
-
- {preview.img && ( -
- {preview.title} window.open(preview.url, '_blank')} - style={{ - maxWidth: preview.img_width ?? '100%', - maxHeight: preview.img_height ?? '100%', - }} - /> -
- )} -
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/LoadMoreReplies.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/LoadMoreReplies.tsx deleted file mode 100644 index b362781c..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/LoadMoreReplies.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { useThreadMessages } from 'app/features/messages/hooks/use-thread-messages'; -import React, { useContext } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import ThreadSection from '../../parts/thread-section'; -import { MessageContext } from '../message-with-replies'; -import { NodeMessage } from 'app/features/messages/types/message'; - -export default (props: { firstMessageId: string; onFirstMessageChanged: (item: NodeMessage) => void }) => { - const context = useContext(MessageContext); - const { message } = useMessage(context); - const { messages, window, loadMore } = useThreadMessages({ - companyId: context.companyId, - threadId: message.thread_id, - }); - - const loadMoreMessages = async (direction: 'history' | 'future') => { - const messages = await loadMore(direction, 10, props.firstMessageId); - props.onFirstMessageChanged && props.onFirstMessageChanged(messages[0]); - }; - - return ( - <> - {!(window.reachedStart && window.start === props.firstMessageId) && - window.end && - messages.length < message.stats.replies && ( - -
- loadMoreMessages('history')} className="link"> - {Languages.t('scenes.apps.messages.message.show_responses_button')} ( - {Math.max(message.stats.replies, messages.length)}) - -
-
- )} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/MessageAttachments.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/MessageAttachments.tsx deleted file mode 100644 index 85aff423..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/MessageAttachments.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Row } from 'antd'; -import { useUploadZones } from 'app/features/files/hooks/use-upload-zones'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import 'moment-timezone'; -import { useContext, useEffect } from 'react'; -import { MessageContext } from '../message-with-replies'; -import PossiblyPendingAttachment from './PossiblyPendingAttachment'; - -export default () => { - const context = useContext(MessageContext); - const { message, save } = useMessage(context); - - const { files, setFiles } = useUploadZones(`message-${message.id}`); - - useEffect(() => { - setFiles(message.files || []); - }, [message.files]); - - useEffect(() => { - const uploaded = files.filter(f => f.metadata?.source !== 'pending'); - const inMessage = (message.files || []).filter(f => f.metadata?.source !== 'pending'); - if (uploaded.length > inMessage.length) { - save({ ...message, files: uploaded }); - } - }, [files]); - - if (files.length === 0) { - return <>; - } - - return ( - - {files - .filter(f => f.metadata) - .map(file => ( - 0} - large={ - //If all the documents are images - files.length <= 6 && - files.filter( - file => - file.metadata?.source === 'internal' && - (file.metadata?.thumbnails?.length || 0) > 0, - ).length === files.length - } - onRemove={() => setFiles(files.filter(f => f.id !== file.id))} - /> - ))} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/MessageContent.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/MessageContent.tsx deleted file mode 100644 index 372911a3..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/MessageContent.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import { useIsChannelMember } from 'app/features/channels/hooks/use-channel'; -import PseudoMarkdownCompiler from 'app/features/global/services/pseudo-markdown-compiler-service'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { useVisibleMessagesEditorLocation } from 'app/features/messages/hooks/use-message-editor'; -import { MessageWithReplies } from 'app/features/messages/types/message'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import { useUser } from 'app/features/users/hooks/use-user'; -import User from 'app/features/users/services/current-user-service'; -import MessageQuote from 'app/molecules/message-quote'; -import MessageStatus from 'app/molecules/message-status'; -import QuotedContent, { useQuotedMessage } from 'app/molecules/quoted-content'; -import { ViewContext } from 'app/views/client/main-view/MainContent'; -import classNames from 'classnames'; -import 'moment-timezone'; -import { ReactNode, useContext, useEffect, useState } from 'react'; -import { gotoMessage } from 'src/utils/messages'; -import { MessageContext } from '../message-with-replies'; -import Blocks from './Blocks'; -import DeletedContent from './DeletedContent'; -import LinkPreview from './LinkPreview'; -import MessageForward from './message-forward'; -import MessageAttachments from './MessageAttachments'; -import MessageEdition from './MessageEdition'; -import MessageHeader from './MessageHeader'; -import Options from './Options'; -import Reactions from './Reactions'; -import RetryButtons from './RetryButtons'; - -type Props = { - linkToThread?: boolean; - threadHeader?: string; -}; - -let loadingInteractionTimeout = 0; - -export default (props: Props) => { - const [active, setActive] = useState(false); - const [loadingAction, setLoadingAction] = useState(false); - const [didMouseOver, setDidMouseOver] = useState(false); - - const context = useContext(MessageContext); - const channelId = context.channelId; - const { message } = useMessage(context); - - // Quoted message logic - const quotedMessage = useQuotedMessage(message, context); - const showQuotedMessage = quotedMessage && quotedMessage.thread_id; - let authorName = ''; - const currentRouterWorkspace = useRouterWorkspace(); - const workspaceId = - context.workspaceId === 'direct' ? currentRouterWorkspace : context.workspaceId; - const deletedQuotedMessage = quotedMessage && quotedMessage.subtype === 'deleted'; - - if (showQuotedMessage) { - const author = useUser(quotedMessage.user_id || ''); - authorName = author ? User.getFullName(author) : 'Anonymous'; - } - - const onInteractiveMessageAction = (action_id: string, context: unknown, passives: unknown) => { - const app_id = message.application_id; - const type = 'interactive_message_action'; - const event = action_id; - const data = { - interactive_context: context, - form: passives, - message: message, - }; - WorkspacesApps.notifyApp(app_id, type, event, data); - }; - - const onAction = (type: string, id: string, context: unknown, passives: unknown) => { - if (type === 'interactive_action') { - setLoadingAction(true); - clearTimeout(loadingInteractionTimeout); - loadingInteractionTimeout = window.setTimeout(() => { - setLoadingAction(false); - }, 5000); - onInteractiveMessageAction(id, context, passives); - } - }; - - useEffect(() => { - setLoadingAction(false); - }, [JSON.stringify(message.blocks)]); - - const deleted = message.subtype === 'deleted'; - - const location = `message-${message.id}`; - const { active: editorIsActive } = useVisibleMessagesEditorLocation( - location, - useContext(ViewContext).type, - ); - - const showEdition = !props.linkToThread && editorIsActive; - const messageIsLoading = message._status === 'sending'; - const messageSaveFailed = message._status === 'failed'; - - const isChannelMember = useIsChannelMember(channelId); - const quotedContent = ; - const showMessageStatus = message.user_id === User.getCurrentUserId(); - - return ( -
{ - setDidMouseOver(true); - }} - onClick={() => setActive(false)} - key={`message_container_${message.id}`} - > - - {showQuotedMessage && !showEdition && quotedMessage.channel_id === context.channelId && ( - - gotoMessage( - quotedMessage, - quotedMessage.company_id || context.companyId, - quotedMessage.channel_id || context.channelId, - quotedMessage.workspace_id || workspaceId, - ) - } - /> - )} - {showQuotedMessage && !showEdition && quotedMessage.channel_id !== context.channelId && ( - { - if (isChannelMember) onAction(type, id, context, passives); - }} - className="mb-1" - author={authorName} - message={quotedMessage} - closable={false} - deleted={deletedQuotedMessage} - goToMessage={() => - gotoMessage( - quotedMessage, - quotedMessage.company_id || context.companyId, - quotedMessage.channel_id || context.channelId, - quotedMessage.workspace_id || workspaceId, - ) - } - /> - )} - {!!showEdition && !deleted && ( -
- -
- )} - {!showEdition && ( - { - if (isChannelMember) onAction(type, id, context, passives); - }} - deleted={deleted} - linkToThread={props.linkToThread} - message={message} - className={classNames({ - message_is_loading: messageIsLoading, - 'message-not-sent': messageSaveFailed, - })} - suffix={ - <> - {message?.files && (message?.files?.length || 0) > 0 && } - {!messageSaveFailed && } - {messageSaveFailed && !messageIsLoading && } - - } - /> - )} - {isChannelMember && - !showEdition && - !deleted && - !messageSaveFailed && - didMouseOver && - !messageIsLoading && ( - setActive(true)} - onClose={() => setActive(false)} - threadHeader={props.threadHeader} - key={`options_${message.id}`} - /> - )} - {showMessageStatus && !showEdition && ( - - )} -
- ); -}; - -export const MessageBlockContent = ({ - deleted, - message, - linkToThread, - suffix, - className, - onAction, -}: { - deleted: boolean; - linkToThread?: boolean; - message: MessageWithReplies; - suffix?: ReactNode; - className?: string; - onAction: (type: string, id: string, context: unknown, passives: unknown) => void; -}) => { - return ( -
- {deleted === true ? ( -
- -
- ) : ( - <> -
- {!!linkToThread && message.text} - {!linkToThread && ( - <> - - - )} -
- - {message?.links && - (message?.links?.length || 0) > 0 && - message.links - .filter(link => link && (link.title || link.description || link.img)) - .map((preview, i) => ( - - ))} - - {suffix} - - )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.scss b/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.scss deleted file mode 100644 index 96cb8d5f..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.scss +++ /dev/null @@ -1,18 +0,0 @@ -.message-edition { - padding-top: 4px; - - .message-edition-toolbar { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: center; - - .editor-toolbar { - padding-left: 0; - - .button-group:first-child { - margin-left: -8px; - } - } - } -} diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.tsx deleted file mode 100644 index 6fd5682a..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/MessageEdition.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import 'moment-timezone'; -import MessageInput from '../../input/input'; -import Button from 'components/buttons/button.jsx'; -import AlertManager from 'app/features/global/services/alert-manager-service'; -import Languages from 'app/features/global/services/languages-service'; -import EditorToolbar from 'app/components/rich-text-editor/editor-toolbar'; -import RichTextEditorStateService from 'app/components/rich-text-editor/editor-state-service'; -import './MessageEdition.scss'; -import { EditorState } from 'draft-js'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { MessageContext } from '../message-with-replies'; -import { ViewContext } from 'app/views/client/main-view/MainContent'; -import { useVisibleMessagesEditorLocation } from 'app/features/messages/hooks/use-message-editor'; - -type Props = { - /** - * The editor plugins to enable during edition - */ - editorPlugins?: string[]; -}; - -export default (props: Props) => { - const context = useContext(MessageContext); - const { message, remove, save: updateMessage } = useMessage(context); - - const location = `message-${context.id}`; - const subLocation = useContext(ViewContext).type; - const editorId = `thread:${context.threadId}/message:${context.id}`; - const { close } = useVisibleMessagesEditorLocation(location, subLocation); - - const editorPlugins = props.editorPlugins || ['emoji', 'mention', 'channel']; - const format = 'markdown'; - - const [editorState, setEditorState] = useState(); - const [isReady, setReady] = useState(false); - - useEffect(() => { - (async () => { - const dataParser = RichTextEditorStateService.getDataParser(editorPlugins); - const initialContent = message?.text || ''; - - setEditorState(() => - RichTextEditorStateService.get(editorId, { - plugins: editorPlugins, - clearIfExists: true, - initialContent: dataParser.fromString(initialContent, format), - }), - ); - - setReady(true); - })(); - }, []); - - const getContentOutput = (editorState: EditorState) => { - return RichTextEditorStateService.getDataParser(editorPlugins).toString(editorState, format); - }; - - const save = () => { - let content = null; - if (editorState) content = getContentOutput(editorState); - if (!content) { - AlertManager.confirm( - () => { - remove(); - }, - () => undefined, - { - title: Languages.t('scenes.apps.messages.chatbox.chat.delete_message_btn', [], 'Delete'), - }, - ); - } else { - updateMessage({ ...message, text: content }); - } - close(); - }; - - const setRichTextEditorState = (editorState: EditorState): void => { - setEditorState(editorState); - RichTextEditorStateService.set(editorId, editorState); - }; - - return !isReady || !editorState ? ( - <> - ) : ( -
- save()} - editorState={editorState} - onChange={editorState => { - setRichTextEditorState(editorState); - }} - /> - -
-
-
- { - setRichTextEditorState(editorState); - }} - /> -
-
-
- - - -
-
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/MessageHeader.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/MessageHeader.tsx deleted file mode 100644 index fe5436eb..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/MessageHeader.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React, { ReactNode, useContext, useState } from 'react'; - -import 'moment-timezone'; -import Moment from 'react-moment'; -import { Typography } from 'antd'; -import classNames from 'classnames'; -import { AlertTriangle } from 'react-feather'; -import { useRecoilState } from 'recoil'; - -import User from 'app/features/users/services/current-user-service'; -import MenusManager from 'app/components/menus/menus-manager.jsx'; -import UserCard from 'app/components/user-card/user-card'; -import Emojione from 'components/emojione/emojione'; -import RouterServices from 'app/features/router/services/router-service'; -import { NodeMessage } from 'app/features/messages/types/message'; -import Languages from 'app/features/global/services/languages-service'; -import Loader from 'components/loader/loader.jsx'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { MessageContext } from '../message-with-replies'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import useRouterChannel from 'app/features/router/hooks/use-router-channel'; -import { useUser } from 'app/features/users/hooks/use-user'; -import { UserType } from 'app/features/users/types/user'; -import { CompanyApplicationsStateFamily } from 'app/features/applications/state/company-applications'; -import { useDirectChannels } from 'app/features/channels/hooks/use-direct-channels'; -import { addUrlTryDesktop } from 'app/views/desktop-redirect'; - -type Props = { - linkToThread?: boolean; -}; - -const { Link } = Typography; - -export default (props: Props) => { - const channelId = useRouterChannel(); - const workspaceId = useRouterWorkspace(); - const [messageLink, setMessageLink] = useState(''); - const { openDiscussion } = useDirectChannels(); - - const context = useContext(MessageContext); - const { message } = useMessage(context); - const parentMessage: NodeMessage | null = useMessage({ - ...context, - id: message.thread_id, - }).message; - - const user = useUser(message.user_id); - - const companyApplications = - useRecoilState(CompanyApplicationsStateFamily(context.companyId))[0] || []; - const application = companyApplications.find(a => a.id === message.application_id); - - const scrollToMessage = () => { - if (message.thread_id !== message.id) { - //TODO messageService.scrollTo({ id: message.thread_id }); - } - }; - - const updateMessageLink = () => { - const url = RouterServices.generateRouteFromState({ - workspaceId: workspaceId, - channelId: channelId, - messageId: message.thread_id || message.id, - }); - setMessageLink(url); - }; - - let userNameRef: ReactNode = null; - const displayUserCard = () => { - if (user) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const box = (window as any).getBoundingClientRect(userNameRef); - MenusManager.openMenu( - [ - { - type: 'react-element', - reactElement: () => ( - openDiscussion([(user as UserType).id || ''])} - /> - ), - }, - ], - box, - null, - { margin: 8 }, - ); - } - }; - - const icon = user?.status ? user.status.split(' ')[0] : undefined; - const status = user?.status ? user.status.split(' ').splice(1).join(' ') : undefined; - return ( -
-
- (userNameRef = node as ReactNode)} - onClick={() => displayUserCard()} - > - {message.override?.title || - (!!user && User.getFullName(user)) || - (message.application_id && application?.identity?.name)} - - {!!user && ( -
- {!!icon && } {!!status && status} -
- )} - {props.linkToThread && ( - - {Languages.t('scenes.apps.messages.input.replied_to')} - scrollToMessage()}>{parentMessage?.text} - - )} - {message.created_at && ( - updateMessageLink()} - rel="noreferrer" - > - 12 * 60 * 60 * 1000 ? 'lll' : 'LT' - } - > - {message.created_at} - - - {message.edited?.edited_at && ( - - {' '} - - {Languages.t('scenes.apps.messages.input.edited', [], 'Edited')} - - )} - - )} -
- {message._status === 'sending' && ( -
- -
- )} - {message._status === 'failed' && ( -
- -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/Options.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/Options.tsx deleted file mode 100644 index 76e0f97b..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/Options.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import React, { useContext } from 'react'; -import 'moment-timezone'; -import { MoreHorizontal, Smile, ArrowUpRight, Trash2, CornerDownLeft } from 'react-feather'; - -import EmojiPicker from 'components/emoji-picker/emoji-picker.jsx'; -import Menu from 'components/menus/menu.jsx'; -import MenusManager from 'app/components/menus/menus-manager.jsx'; -import Languages from 'app/features/global/services/languages-service'; -import AlertManager from 'app/features/global/services/alert-manager-service'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service'; -import User from 'app/features/users/services/current-user-service'; -import RouterServices from 'app/features/router/services/router-service'; -import { Application } from 'app/features/applications/types/application'; -import { getCompanyApplications } from 'app/features/applications/state/company-applications'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import { MessageContext } from '../message-with-replies'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import useRouterChannel from 'app/features/router/hooks/use-router-channel'; -import { useVisibleMessagesEditorLocation } from 'app/features/messages/hooks/use-message-editor'; -import { ViewContext } from 'app/views/client/main-view/MainContent'; -import SideViewService from 'app/features/router/services/side-view-service'; -import Emojione from 'app/components/emojione/emojione'; -import { useChannel } from 'app/features/channels/hooks/use-channel'; -import { useEphemeralMessages } from 'app/features/messages/hooks/use-ephemeral-messages'; -import { copyToClipboard } from 'app/features/global/utils/CopyClipboard'; -import { addUrlTryDesktop } from 'app/views/desktop-redirect'; -import { useMessageQuoteReply } from 'app/features/messages/hooks/use-message-quote-reply'; -import { useMessageSeenBy } from 'app/features/messages/hooks/use-message-seen-by'; -import { EmojiSuggestionType } from 'app/components/rich-text-editor/plugins/emoji'; -import { MessagesListContext } from '../../messages-list'; -import { useSetRecoilState } from 'recoil'; -import { ForwardMessageAtom } from 'app/components/forward-message'; - -type Props = { - onOpen?: () => void; - onClose?: () => void; - threadHeader?: string; -}; - -export default (props: Props) => { - const channelId = useRouterChannel(); - const workspaceId = useRouterWorkspace(); - const context = useContext(MessageContext); - const listContext = useContext(MessagesListContext); - const { message, react, remove, pin } = useMessage(context); - const { channel } = useChannel(channelId); - const { message: thread } = useMessage({ - companyId: channel.company_id || '', - threadId: message.thread_id, - id: message.thread_id, - }); - const { remove: removeLastEphemeral } = useEphemeralMessages({ - companyId: context.companyId, - channelId: channelId, - }); - const location = `message-${message.id}`; - const subLocation = useContext(ViewContext).type; - const { set: setVisibleEditor } = useVisibleMessagesEditorLocation(location, subLocation); - - const { set: setQuoteReply } = useMessageQuoteReply(channelId); - const setForwardMessage = useSetRecoilState(ForwardMessageAtom); - - const { openSeenBy } = useMessageSeenBy(); - - const menu: Record void)>[] = []; - - const triggerApp = (app: Application) => { - const data = { - channel: channel, - thread: thread.id && thread.id !== message.id ? thread : null, - message: message, - }; - WorkspacesApps.notifyApp(app.id, 'action', 'action', data); - }; - - const onOpen = (evt: Event) => { - props.onOpen && props.onOpen(); - if (evt) { - evt.preventDefault(); - evt.stopPropagation(); - } - }; - - if (message.ephemeral) { - menu.push({ - type: 'menu', - icon: 'trash', - text: Languages.t('scenes.apps.messages.message.remove_button', [], 'Delete'), - className: 'error', - onClick: () => { - removeLastEphemeral(); - }, - }); - } else { - if (channel && channel.visibility !== 'direct') { - menu.push({ - type: 'menu', - icon: 'arrow-up-right', - text: Languages.t('scenes.apps.messages.message.show_button', [], 'Display'), - onClick: () => { - SideViewService.select(channel?.id || '', { - app: { identity: { code: 'messages' } } as Application, - context: { - viewType: 'channel_thread', - threadId: message.thread_id, - }, - }); - }, - }); - } - - menu.push({ - type: 'menu', - icon: 'link', - text: Languages.t('scenes.apps.messages.message.copy_link', [], 'Copy link to message'), - onClick: () => { - const url = addUrlTryDesktop( - `${document.location.origin}${RouterServices.generateRouteFromState({ - workspaceId: workspaceId, - channelId: channelId, - threadId: message.thread_id, - messageId: message.id, - })}`, - ); - - copyToClipboard(url); - }, - }); - - if (channel && channel.visibility === 'direct' && !listContext.readonly) { - menu.push({ - type: 'menu', - icon: 'corner-down-left', - text: Languages.t('scenes.apps.messages.message.reply_button', [], 'Reply'), - onClick: () => { - setQuoteReply({ message: message.thread_id, channel: channelId }); - }, - }); - } - - menu.push({ - type: 'menu', - icon: 'comment-info', - text: Languages.t('components.message_seen_by.btn', [], 'Information'), - onClick: () => { - openSeenBy({ - message_id: message.id, - company_id: context.companyId, - thread_id: message.thread_id, - workspace_id: context.workspaceId, - }); - }, - }); - - if (!message.context?.disable_pin && !listContext.readonly) - menu.push({ - type: 'menu', - icon: 'map-pin', - text: Languages.t( - !message.pinned_info?.pinned_at - ? 'scenes.apps.messages.message.pin_button' - : 'scenes.apps.messages.message.unpin_button', - [], - 'Pin message', - ), - className: 'option_button', - onClick: () => { - pin(!message.pinned_info?.pinned_at); - }, - }); - - menu.push({ - type: 'menu', - icon: 'envelope-send', - text: Languages.t('scenes.apps.messages.message.forward'), - className: 'option_button', - onClick: () => { - setForwardMessage({ - id: message.id, - thread_id: message.thread_id, - channel_id: channelId, - workspace_id: context.workspaceId, - company_id: context.companyId, - }); - }, - }); - - const apps = - getCompanyApplications(Groups.currentGroupId).filter( - (app: Application) => app.display?.twake?.chat?.actions?.length, - ) || []; - - if (apps.length > 0 && !listContext.readonly) { - menu.push({ type: 'separator' }); - menu.push({ - type: 'react-element', - reactElement: () => { - return apps.map((app: Application) => { - return ( -
{ - triggerApp(app); - }} - > -
-
- {app?.display?.twake?.chat?.actions?.[0].description || app.identity?.name} -
-
- ); - }); - }, - }); - } - - if ( - (message.user_id === User.getCurrentUserId() || - (message.application_id && message.context?.allow_delete === 'everyone') || - (message.application_id && - WorkspaceUserRights.hasWorkspacePrivilege() && - message.context?.allow_delete === 'administrators')) && - !listContext.readonly - ) { - if (menu.length > 0 && (!message.application_id || !message?.stats?.replies)) { - menu.push({ type: 'separator' }); - } - if (!message.application_id) { - menu.push({ - type: 'menu', - icon: 'edit-alt', - text: Languages.t('scenes.apps.messages.message.modify_button', [], 'Edit'), - onClick: () => { - setVisibleEditor({ location, subLocation }); - }, - }); - } - if (message?.stats?.replies <= 1) { - menu.push({ - type: 'menu', - icon: 'trash-alt', - text: Languages.t('scenes.apps.messages.message.remove_button', [], 'Delete'), - className: 'error', - onClick: () => { - AlertManager.confirm(() => remove()); - }, - }); - } - } - } - - if (message.ephemeral) { - return ( -
-
{ - removeLastEphemeral(); - }} - > - -
-
- ); - } - - const userReactions = (message.reactions || [])?.filter(r => - r.users.includes(User.getCurrentUserId()), - ); - - return ( -
- {/*!props.threadHeader && ( -
-
- -
-
- )*/} -
- {!listContext.readonly && ( - <> - {[':heart:', ':+1:', ':eyes:', ':tada:'].map(emoji => ( - <> -
m.name).includes(emoji) ? 'active' : '') - } - onClick={() => react([emoji], 'toggle')} - > - -
-
- - ))} - - onOpen(evt)} - menu={[ - { - type: 'react-element', - className: 'menu-cancel-margin', - reactElement: () => { - return ( - e.name) || []} - onChange={(emoji: EmojiSuggestionType) => { - MenusManager.closeMenu(); - props.onClose && props.onClose(); - react([emoji.colons], 'toggle'); - }} - /> - ); - }, - }, - ]} - position="top" - > - - -
- - )} - - {!props.threadHeader && channel && channel.visibility !== 'direct' && ( - <> -
{ - SideViewService.select(channelId, { - app: { identity: { code: 'messages' } } as Application, - context: { - viewType: 'channel_thread', - threadId: message.thread_id || message.id, - }, - }); - }} - > - -
-
- - )} - - {channel && channel.visibility === 'direct' && !listContext.readonly && ( - <> -
{ - setQuoteReply({ message: message.thread_id, channel: channelId }); - }} - > - -
-
- - )} - - onOpen(evt)} - onClose={() => props.onClose && props.onClose()} - menu={menu} - position={'left'} - > - - -
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx deleted file mode 100644 index 066afb5a..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { useUpload } from 'app/features/files/hooks/use-upload'; -import FileComponent from 'app/components/file/file-component'; -import { DataFileType } from 'app/components/file/types'; -import { PendingFileRecoilType } from 'app/features/files/types/file'; -import FileUploadAPIClient from 'app/features/files/api/file-upload-api-client'; -import { MessageFileType } from 'app/features/messages/types/message'; - -type PropsType = { - file: MessageFileType; - onRemove?: () => void; - type: 'input' | 'message'; - large?: boolean; - xlarge?: boolean; -}; - -export default ({ file, onRemove, type, large, xlarge }: PropsType) => { - const { getOnePendingFile } = useUpload(); - - const id = - (typeof file.metadata?.external_id === 'string' - ? file.metadata?.external_id - : file.metadata?.external_id?.id) || ''; - const companyId = - (typeof file.metadata?.external_id === 'string' - ? file.company_id - : file.metadata?.external_id?.company_id) || ''; - - let status: PendingFileRecoilType['status'] | undefined = 'success'; - let progress = 1; - - let formatedFile: DataFileType = { - id: id, - company_id: companyId, - name: file.metadata?.name || '', - size: file.metadata?.size || 0, - thumbnail: FileUploadAPIClient.getFileThumbnailUrlFromMessageFile(file) || '', - thumbnail_ratio: - (file.metadata?.thumbnails?.[0]?.width || 1) / (file.metadata?.thumbnails?.[0]?.height || 1), - type: FileUploadAPIClient.mimeToType(file.metadata?.mime || ''), - }; - - if (file?.metadata?.source === 'pending') { - const pendingFile = getOnePendingFile(id); - if (!pendingFile) { - if (onRemove) onRemove(); - return <>; - } - - formatedFile = { - id: pendingFile?.backendFile?.id || '', - company_id: pendingFile?.backendFile?.company_id || '', - name: pendingFile?.originalFile.name || '', - size: pendingFile?.originalFile.size || 0, - thumbnail: URL.createObjectURL(pendingFile.originalFile), - thumbnail_ratio: 1, - type: FileUploadAPIClient.mimeToType(pendingFile?.originalFile.type || ''), - }; - status = pendingFile.status || undefined; - progress = pendingFile.progress; - } - - return formatedFile ? ( - - ) : ( - <> - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/Reactions.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/Reactions.tsx deleted file mode 100644 index 8a88874d..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/Reactions.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useContext } from 'react'; -import User from 'app/features/users/services/current-user-service'; -import 'moment-timezone'; -import Emojione from 'components/emojione/emojione'; -import { ReactionType } from 'app/features/messages/types/message'; -import { Tooltip } from 'antd'; -import classNames from 'classnames'; -import { MessageContext } from '../message-with-replies'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { getUser } from 'app/features/users/hooks/use-user-list'; -import { MessagesListContext } from '../../messages-list'; - -export default () => { - const context = useContext(MessageContext); - const { message, react } = useMessage(context); - - if ( - !(!message?.context?.disable_reactions && message?.reactions && message?.reactions.length > 0) - ) - return <>; - - return ( -
- {(message?.reactions || []) - .map(r => r) //To avoid modifing the original one with sort - .sort((a, b) => b.count || 0 - a.count) - .map((reaction, index) => ( - - ))} -
- ); -}; - -const Reaction = ({ - reaction, - react, -}: { - reaction: ReactionType; - react: (emojis: string[], mode?: 'add' | 'toggle' | 'remove' | 'replace') => Promise; -}): JSX.Element => { - const listContext = useContext(MessagesListContext); - const noReactions: boolean = (reaction.count || 0) <= 0; - const users: ReactionType['users'] = reaction.users || []; - - if (noReactions) return <>; - - const reactionClassName = classNames('reaction', { - is_selected: reaction.users.includes(User.getCurrentUserId()), - }); - - return ( - } - > -
{ - if (!listContext.readonly) react([reaction.name], 'toggle'); - }} - > - - {reaction.count} -
-
- ); -}; - -const ReactionTooltip = ({ users }: { users: ReactionType['users'] }): JSX.Element => ( - <> - {users.map(id => { - const user = getUser(id); - - if (!user) return <>; - - const name = User.getFullName(user); - - return ( -
- {name} -
- ); - })} - -); diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/ReplyBlock.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/ReplyBlock.tsx deleted file mode 100644 index b292cc52..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/ReplyBlock.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useContext } from 'react'; -import { CornerDownRight } from 'react-feather'; -import ThreadSection from '../../parts/thread-section'; -import Languages from 'app/features/global/services/languages-service'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { MessageContext } from '../message-with-replies'; -import { useVisibleMessagesEditorLocation } from 'app/features/messages/hooks/use-message-editor'; -import { ViewContext } from 'app/views/client/main-view/MainContent'; -import Input from '../../input/input'; -import useRouterChannel from 'app/features/router/hooks/use-router-channel'; -import { getChannel, useIsChannelMember } from 'app/features/channels/hooks/use-channel'; - - -export default () => { - const context = useContext(MessageContext); - const channelId = useRouterChannel(); - const { message } = useMessage(context); - const channel = getChannel(channelId); - - const location = `thread-${message.thread_id}`; - const subLocation = useContext(ViewContext).type; - const { active: editorIsActive, set: setVisibleEditor } = useVisibleMessagesEditorLocation( - location, - subLocation, - ); - - const isChannelMember = useIsChannelMember(channelId); - - if (!isChannelMember) { - return <>; - } - - if (message.subtype === 'deleted' || message.thread_id != message.id) { - return <>; - } - - if (channel?.visibility === 'direct') { - return <>; - } - - if (editorIsActive) { - return ( - -
- -
-
- ); - } - - return ( - -
- - setVisibleEditor({ - location, - subLocation, - }) - } - > - {' '} - {Languages.t('scenes.apps.messages.message.reply_button')} - -
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/RetryButtons.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/RetryButtons.tsx deleted file mode 100644 index f0af9fe0..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/RetryButtons.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { useMessageEditor } from 'app/features/messages/hooks/use-message-editor'; -import React, { useContext } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import { MessageContext } from '../message-with-replies'; - -export default () => { - const context = useContext(MessageContext); - const { message } = useMessage(context); - const { retry, cancel } = useMessageEditor(context); - - return ( -
- retry(message)} - > - {Languages.t('general.retry')} - - cancel(message)}> - {Languages.t('general.remove')} - -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/files.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/files.tsx deleted file mode 100644 index 2027996a..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/files.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Row } from 'antd'; -import { MessageFileType } from 'app/features/messages/types/message'; -import PossiblyPendingAttachment from '../PossiblyPendingAttachment'; - -export const ForwardedFiles = (props: { files: MessageFileType[] }) => { - return ( - - {props.files - .filter(f => f.metadata) - .map(file => ( - - ))} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/index.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/index.tsx deleted file mode 100644 index 53588dee..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/parts/message-forward/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Info } from 'app/atoms/text'; -import Languages from 'app/features/global/services/languages-service'; -import { XIcon } from '@atoms/icons-agnostic'; -import { MessageWithReplies, NodeMessage } from 'app/features/messages/types/message'; -import { UserType } from 'app/features/users/types/user'; -import { MessageBlockContent } from '../MessageContent'; -import { ForwardedFiles } from './files'; - -type PropsType = { - message: NodeMessage & { - users?: UserType[] | undefined; - company_id: string; - workspace_id: string; - channel_id: string; - thread_id: string; - id: string; - }; - author: string; - closable?: boolean; - deleted?: boolean; - goToMessage?: () => void; - onClose?: () => void; - className?: string; - onAction: (type: string, id: string, context: unknown, passives: unknown) => void; -}; - -export default ({ - author, - message, - closable = true, - onClose, - deleted = false, - goToMessage, - className = '', - onAction, -}: PropsType) => { - const clickable = !closable; - - return ( -
{}} - > -
-
-

{author}

-
- {deleted ? ( - {Languages.t('molecules.message_quote.deleted')} - ) : ( - {message.files && }} - /> - )} -
-
- {closable && onClose && ( -
- { - e.stopPropagation(); - onClose(); - }} - /> -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/responses.tsx b/twake/frontend/src/app/views/applications/messages/message/responses.tsx deleted file mode 100644 index fd5ba94c..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/responses.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { useThreadMessages } from 'app/features/messages/hooks/use-thread-messages'; -import Message from './message'; -import { MessageContext } from './message-with-replies'; -import ThreadSection from '../parts/thread-section'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import Numbers from 'app/features/global/utils/Numbers'; - -type Props = { - companyId: string; - workspaceId: string; - channelId: string; - threadId: string; - firstMessageId: string; -}; - -export default ({ threadId, companyId, channelId, workspaceId, firstMessageId }: Props) => { - const { messages } = useThreadMessages({ companyId, threadId }); - - return ( - <> - {messages - .filter(m => !firstMessageId || Numbers.compareTimeuuid(m.id, firstMessageId) >= 0) - .filter(m => m.threadId !== m.id) - .map(m => { - return ( - - - - ); - })} - - ); -}; - -const Reply = () => { - const context = React.useContext(MessageContext); - const { message } = useMessage(context); - return ( - - - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/message/time-separator.tsx b/twake/frontend/src/app/views/applications/messages/message/time-separator.tsx deleted file mode 100644 index 9f6dbff5..00000000 --- a/twake/frontend/src/app/views/applications/messages/message/time-separator.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import Moment from 'react-moment'; -import 'moment-timezone'; -import './message.scss'; - -type Props = { - date: number; -}; - -export default React.memo((props: Props) => { - return ( -
-
-
-
- {new Date().getTime() - props.date > 24 * 60 * 60 * 1000 ? ( - - ) : ( - - )} -
-
-
-
- ); -}); diff --git a/twake/frontend/src/app/views/applications/messages/messages-list.tsx b/twake/frontend/src/app/views/applications/messages/messages-list.tsx deleted file mode 100644 index 5f0661d1..00000000 --- a/twake/frontend/src/app/views/applications/messages/messages-list.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useChannelMessages } from 'app/features/messages/hooks/use-channel-messages'; -import ListBuilder, { ListBuilderHandle } from './list-builder'; -import TimeSeparator from './message/time-separator'; -import MessageWithReplies from './message/message-with-replies'; -import FirstMessage from './message/parts/FirstMessage/FirstMessage'; -import LockedHistoryBanner from 'app/components/locked-features-components/locked-history-banner/locked-history-banner'; -import MessageHistoryService from 'app/features/messages/services/message-history-service'; -import { useCurrentCompany } from 'app/features/companies/hooks/use-companies'; -import ChannelAPIClient from 'app/features/channels/api/channel-api-client'; -import { - MessagesAndComponentsType, - withNonMessagesComponents, -} from '../../../features/messages/hooks/with-non-messages-components'; -import { useHighlightMessage } from 'app/features/messages/hooks/use-highlight-message'; -import SideViewService from 'app/features/router/services/side-view-service'; -import { Application } from 'app/features/applications/types/application'; -import GoToBottom from './parts/go-to-bottom'; -import { MessagesPlaceholder } from './placeholder'; -import { cleanFrontMessagesFromListOfMessages } from 'app/features/messages/hooks/use-message-editor'; -import { getMessage } from 'app/features/messages/hooks/use-message'; -import messageApiClient from 'app/features/messages/api/message-api-client'; -import User from 'app/features/users/services/current-user-service'; -import { useChannelMembersReadSections } from 'app/features/channel-members/hooks/use-channel-members-read-sections'; -import { delayRequest } from 'app/features/global/utils/managedSearchRequest'; -import { useRefreshPublicOrPrivateChannels } from 'app/features/channels/hooks/use-public-or-private-channels'; -import { usePageVisibility } from "react-page-visibility"; - -type Props = { - companyId: string; - workspaceId?: string; - channelId?: string; - threadId?: string; - readonly?: boolean; -}; - -export const MessagesListContext = React.createContext({ - hideReplies: false, - withBlock: false, - readonly: false, -}); - -export default ({ channelId, companyId, workspaceId, readonly }: Props) => { - const listBuilderRef = useRef(null); - const [atBottom, setAtBottom] = useState(true); - const { seen, refresh: loadReadSections } = useChannelMembersReadSections( - companyId, - workspaceId || 'direct', - channelId || '', - ); - - const { - messages: _messages, - loadMore, - window, - jumpTo, - convertToKeys, - } = useChannelMessages({ - companyId, - workspaceId: workspaceId || '', - channelId: channelId || '', - }); - let messages = _messages; - const { company } = useCurrentCompany(); - const shouldLimit = MessageHistoryService.shouldLimitMessages( - company, - messages[0]?.id || '', - messages.length, - ); - messages = withNonMessagesComponents(messages, window.reachedStart, shouldLimit); - - const loadMoreMessages = async ( - direction: 'history' | 'future', - limit?: number, - offsetItem?: MessagesAndComponentsType, - ) => { - const messages = await loadMore(direction, limit, offsetItem?.threadId); - return withNonMessagesComponents( - convertToKeys(company.id, messages), - window.reachedStart && direction === 'history', - shouldLimit && direction === 'history', - ); - }; - - const { refresh: refreshChannels } = useRefreshPublicOrPrivateChannels(); - const isPageVisible = usePageVisibility(); - - useEffect(() => { - if (messages.length === 0) loadMore('history'); - }, []); - - useEffect(() => { - if (window.reachedEnd && atBottom && messages.length > 0 && isPageVisible) { - const seenMessages = messages.filter(message => { - const m = getMessage(message.id || message.threadId); - const currentUserId = User.getCurrentUserId(); - - if (m.user_id === currentUserId) { - return false; - } - - return m.status === 'delivered' || (m.status === 'read' && !seen(currentUserId, m.id)); - }); - if (seenMessages.length > 0) { - delayRequest('message-list-read-request', async () => { - await messageApiClient.read( - companyId, - channelId || '', - workspaceId || 'direct', - seenMessages, - ); - await loadReadSections(); - }); - } - } - }, [messages, messages.length, window.reachedEnd, isPageVisible]); - - const { highlight, cancelHighlight, reachedHighlight } = useHighlightMessage(); - - useEffect(() => { - //Manage scroll to highlight - if (listBuilderRef.current && highlight && !highlight.reachedThread) { - if (highlight.answerId) { - SideViewService.select(channelId || '', { - app: { identity: { code: 'messages' } } as Application, - context: { - viewType: 'channel_thread', - threadId: highlight.threadId, - }, - }); - } - - // Find the correct index of required message - const index = messages.findIndex(m => m.id === highlight.threadId); - if (index < 0) { - // Load the right portion of messages - jumpTo(highlight.threadId); - return; - } - setTimeout(() => { - if (listBuilderRef.current) - listBuilderRef.current.scrollToIndex({ - align: 'start', - index: index, - }); - setTimeout(() => { - //Need to wait a bit for the scroll to ends - reachedHighlight('thread'); - }, 1000); - }, 1000); - } - }, [highlight, messages.length]); - - useEffect(() => { - if (messages.length) { - ChannelAPIClient.read(companyId, workspaceId || '', channelId || '', { status: true }).then( - () => { - refreshChannels(); - }, - ); - } - }, [messages.length > 0]); - - useEffect(() => { - if (messages.length) { - delayRequest('message-list-load-read-sections', async () => { - loadReadSections(); - }); - } - }, [companyId, workspaceId, channelId, messages.length > 0]); - - const row = React.useMemo( - () => (_: number, m: MessagesAndComponentsType) => { - if (m.type === 'timeseparator') { - return ( -
- -
- ); - } - - if (m.type === 'header') { - return ( -
- -
- ); - } - - if (m.type === 'locked') { - return ( -
- -
- ); - } - - return ( -
- -
- ); - }, - [], - ); - - const jumpToBottom = () => { - if (window.reachedEnd) { - listBuilderRef.current?.scrollTo({ top: 10000000, behavior: 'smooth' }); - } else { - // Load the right portion of messages - jumpTo(''); - } - }; - - //This hide virtuoso but it start to work in backend - const virtuosoLoading = highlight && !highlight?.reachedThread; - - return ( - - {(!window.loaded || virtuosoLoading) && } - {!window.loaded &&
} - {window.loaded && ( - ) => { - const scrollBottom = - (e.target as HTMLElement).scrollHeight - - (e.target as HTMLElement).scrollTop - - (e.target as HTMLElement).clientHeight; - const closeToBottom = scrollBottom < 500; - if (closeToBottom !== atBottom) setAtBottom(closeToBottom); - cancelHighlight(); - }} - items={messages} - filterOnAppend={messages => { - return cleanFrontMessagesFromListOfMessages(messages); - }} - itemId={m => m.type + (getMessage(m.id)?.context?._front_id || m.threadId) + m.id} - emptyListComponent={} - itemContent={row} - followOutput={!!window.reachedEnd && 'smooth'} - loadMore={loadMoreMessages} - atBottomStateChange={async (atBottom: boolean) => { - if (atBottom && window.reachedEnd) { - setAtBottom(true); - await ChannelAPIClient.read(companyId, workspaceId || '', channelId || '', { - status: true, - }); - refreshChannels(); - } - }} - /> - )} - {!(atBottom && window.reachedEnd) && window.loaded && messages.length > 0 && ( - { - jumpToBottom(); - }} - /> - )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/messages.scss b/twake/frontend/src/app/views/applications/messages/messages.scss deleted file mode 100644 index 47dd4fb7..00000000 --- a/twake/frontend/src/app/views/applications/messages/messages.scss +++ /dev/null @@ -1,281 +0,0 @@ -.messages-view { - position: relative; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - - .loading-blink { - @keyframes loading-blink { - 50% { - opacity: 0.5; - } - 20%, - 80% { - opacity: 1; - } - } - animation: loading-blink 1s infinite; - } - - .go-to-bottom { - position: relative; - padding: 0 8px; - bottom: 46px; - margin-bottom: -32px; - height: 28px; - width: fit-content; - z-index: 4; - font-size: 14px; - color: var(--white); - align-items: center; - align-self: center; - display: flex; - flex-direction: row; - justify-content: center; - background-color: var(--primary); - border-radius: 14px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); - cursor: pointer; - transition: opacity 0.2s; - opacity: 1; - } - - .user-writing-info-message-view { - position: relative; - margin: 0 auto; - min-width: 0; - max-width: 1000px; - width: calc(100% - 32px); - position: relative; - - .user-writing-info-message-view-inner { - position: absolute; - display: flex; - justify-content: flex-start; - align-items: center; - color: var(--grey-dark); - bottom: -8px; - left: 0px; - font-size: 11px; - pointer-events: none; - background: #ffffff88; - border-radius: 4px; - } - } - - .loading-full { - position: absolute; - width: 100%; - height: 100%; - background: #fff; - z-index: 1; - .loading { - position: absolute; - margin: auto; - width: 50px; - height: 50px; - right: 0px; - bottom: 0px; - top: 0px; - left: 0px; - } - } - - .messages-list { - display: flex; - flex-direction: column; - - .header { - display: flex; - justify-content: center; - - .loader { - margin: 10px; - } - } - - .footer { - display: flex; - justify-content: center; - - .loader { - margin: 10px; - } - } - - &:not(.scrolled-up) { - .go-to-bottom { - pointer-events: none; - opacity: 0; - } - } - } - - .messages-scroller-parent { - flex: 1; - min-height: 0; - position: relative; - - &:before { - content: ''; - position: absolute; - width: 100%; - height: 0px; - background: var(--white); - border-top: 1px solid transparent; - z-index: 4; - bottom: 0; - pointer-events: none; - transition: height 0.2s; - } - &:after { - content: ''; - position: absolute; - width: 100%; - height: 100%; - box-shadow: 0 -16px 8px -8px rgba(255, 255, 255, 0) inset; - z-index: 4; - bottom: 8px; - pointer-events: none; - transition: box-shadow 0.2s, bottom 0.2s; - } - &.scrolled-up-100 { - &:after, - &:before { - opacity: 1; - } - &:before { - border-top: 1px solid var(--grey-background); - height: 16px; - } - &:after { - bottom: 16px; - box-shadow: 0 -32px 32px -32px rgba(0, 0, 0, 0.1) inset; - } - } - &.scrolled-up { - &:after { - content: ''; - position: absolute; - width: 100%; - height: 100%; - box-shadow: 0 -16px 8px -8px rgba(255, 255, 255, 1) inset; - z-index: 4; - bottom: 0px; - pointer-events: none; - transition: box-shadow 0.2s, bottom 0.2s; - } - } - - .message_header { - margin-top: 16px; - } - } - - .thread .message_timeline { - text-align: left; - background: var(--white); - margin: 0; - width: 100%; - .time_container { - margin-left: 32px; - top: -12px; - } - &::before { - display: none; - } - } - - .time_separator { - height: 24px; - display: flex; - align-self: center; - justify-content: center; - align-items: baseline; - } - - .message_timeline { - text-align: center; - color: var(--grey-dark); - font-size: 12px; - position: relative; - height: 22px; - margin: 0; - padding-bottom: 0px; - padding-top: 0px; - min-width: 0; - max-width: 1000px; - width: calc(100% - 32px); - - &::before { - content: ''; - z-index: 0; - width: 100%; - border-bottom: 1px solid var(--grey-background); - position: relative; - left: 0; - margin: 0; - display: inline-block; - } - - &.new_messages { - &::before { - border-color: var(--primary); - } - .time_container { - .time { - font-weight: 700; - background: var(--white); - color: var(--primary); - border-radius: 8px; - } - } - } - - .time_container { - display: inline-block; - padding: 0px 11px; - background: var(--white); - top: -15px; - position: relative; - border-radius: 8px; - - .time { - color: var(--grey-dark); - width: auto; - z-index: 1; - height: 22px; - line-height: 22px; - margin: auto; - font-size: 12px; - border-radius: 12px; - padding: 0 4px; - } - } - } - - .writing_message { - font-size: 12px; - color: var(--grey-dark); - font-size: 12px; - padding-bottom: 8px; - position: absolute; - bottom: 0; - left: 0; - margin: auto; - right: 0; - width: calc(100% - 48px); - background: #fff; - transition: opacity 0.2s; - min-width: 0; - max-width: 1000px; - width: calc(100% - 32px); - - &:hover { - opacity: 0.2; - } - } -} diff --git a/twake/frontend/src/app/views/applications/messages/messages.tsx b/twake/frontend/src/app/views/applications/messages/messages.tsx deleted file mode 100644 index 12ce06ca..00000000 --- a/twake/frontend/src/app/views/applications/messages/messages.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { Suspense, useState } from 'react'; -import { ChannelType } from 'app/features/channels/types/channel'; -import { ViewConfiguration } from 'app/features/router/services/app-view-service'; -import NewThread from './input/new-thread'; -import MessagesList from './messages-list'; -import ThreadMessagesList from './thread-messages-list'; -import IsWriting from './input/parts/IsWriting'; -import { - useChannel, - useIsChannelMember, - useIsReadOnlyChannel, -} from 'app/features/channels/hooks/use-channel'; -import { Button } from 'app/atoms/button/button'; -import ChannelsReachableAPIClient from 'app/features/channels/api/channels-reachable-api-client'; -import UserService from 'app/features/users/services/current-user-service'; -import * as Text from '@atoms/text'; -import Languages from 'app/features/global/services/languages-service'; -import MessageSeenBy from 'app/components/message-seen-by/message-seen-by'; -import { useUser } from 'app/features/users/hooks/use-user'; -import { UserType } from 'app/features/users/types/user'; -import { ForwardMessageModal } from 'app/components/forward-message'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; - -type Props = { - channel: ChannelType; - tab?: unknown; - options: ViewConfiguration; -}; - -export default (props: Props) => { - if (!props.channel) { - return <>; - } - - const companyId = props.channel.company_id || ''; - const workspaceId = props.channel.workspace_id || ''; - const channelId = props.channel.id || ''; - const isDirectChannel = props.channel.visibility !== 'direct'; - const threadId = props.options.context?.threadId || ''; - const isChannelMember = useIsChannelMember(channelId); - const currentUser = UserService.getCurrentUser(); - let userIsNotInCompany = false; - const otherChannelsMembersThanMe = - (props.channel.members || []).filter(id => id !== currentUser?.id) || []; - const otherUserThatIsNotMe = useUser(otherChannelsMembersThanMe[0] || ''); - if ( - otherUserThatIsNotMe && - otherChannelsMembersThanMe.length === 1 && - !UserService.isInCompany(otherUserThatIsNotMe as UserType, companyId) - ) { - userIsNotInCompany = true; - } - - const channelIsRestricted = - useIsReadOnlyChannel(channelId) && - currentUser.id !== props.channel.owner && - !AccessRightsService.hasLevel(workspaceId, 'moderator'); - - return ( -
- - - }> - {!threadId ? ( - - ) : ( - - )}{' '} - - - - {isChannelMember && channelIsRestricted && } - {isChannelMember && !channelIsRestricted && !userIsNotInCompany && ( - - )} - {isChannelMember && userIsNotInCompany && } - {!isChannelMember && } -
- ); -}; - -const JoinChanneBlock = ({ channelId }: { channelId: string }) => { - const [loading, setLoading] = useState(false); - const { channel, refresh } = useChannel(channelId); - - if (!channel) { - return <>; - } - - return ( -
- -
- {Languages.t('scenes.client.join_public_channel.info')} -
- ); -}; - -const UserIsNotInCompany = () => { - return ( -
- {Languages.t('scenes.apps.messages.message.user_deactivated')} -
- ); -}; - -const ChannelIsRestricted = () => { - return ( -
- {Languages.t('scenes.client.readonly.info')} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/parts/go-to-bottom.tsx b/twake/frontend/src/app/views/applications/messages/parts/go-to-bottom.tsx deleted file mode 100644 index aa553d0e..00000000 --- a/twake/frontend/src/app/views/applications/messages/parts/go-to-bottom.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import { ArrowDown } from 'react-feather'; - -type Props = { - onClick: () => void; - newMessages?: number; -}; - -export default (props: Props) => { - const msg = props.newMessages - ? Languages.t('scenes.apps.messages.messageslist.go_last_message_button.new_messages') - : Languages.t('scenes.apps.messages.messageslist.go_last_message_button'); - - return ( -
props.onClick()}> - - {msg} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/parts/thread-avatar.tsx b/twake/frontend/src/app/views/applications/messages/parts/thread-avatar.tsx deleted file mode 100644 index 7bad7a10..00000000 --- a/twake/frontend/src/app/views/applications/messages/parts/thread-avatar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useContext } from 'react'; -import 'moment-timezone'; -import User from 'app/features/users/services/current-user-service'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import Icon from 'app/components/icon/icon.jsx'; -import './threads.scss'; -import { MessageContext } from '../message/message-with-replies'; -import { useMessage } from 'app/features/messages/hooks/use-message'; -import { useRecoilState } from 'recoil'; -import { CompanyApplicationsStateFamily } from 'app/features/applications/state/company-applications'; -import { useUser } from 'app/features/users/hooks/use-user'; - -type Props = { - small?: boolean; -}; - -export default (props: Props) => { - const context = useContext(MessageContext); - const { message } = useMessage(context); - - const user = - useUser(message.user_id) || (message.users || []).find(u => u.id === message.user_id); - const companyApplications = - useRecoilState(CompanyApplicationsStateFamily(context.companyId))[0] || []; - const application = companyApplications.find(a => a.id === message.application_id); - - return ( - <> - {!!user && !message.application_id && ( -
- )} - {!!message.application_id && ( - - )} - - ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/parts/thread-section.tsx b/twake/frontend/src/app/views/applications/messages/parts/thread-section.tsx deleted file mode 100644 index 719b731d..00000000 --- a/twake/frontend/src/app/views/applications/messages/parts/thread-section.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { ReactNode, Suspense } from 'react'; -import 'moment-timezone'; -import './threads.scss'; -import ThreadAvatar from './thread-avatar'; - -type Props = { - compact?: boolean; - gradient?: boolean; - small?: boolean; - head?: boolean; - alinea?: boolean; - children?: ReactNode; - noSenderSpace?: boolean; - withAvatar?: boolean; - pinned?: boolean; - className?: string; - onClick?: () => void; -}; - -export default (props: Props) => { - return ( -
-
- {!props.noSenderSpace && ( -
- {props.withAvatar && ( - - - - )} -
- )} - {props.children} -
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/parts/thread.tsx b/twake/frontend/src/app/views/applications/messages/parts/thread.tsx deleted file mode 100644 index 8b3ab89d..00000000 --- a/twake/frontend/src/app/views/applications/messages/parts/thread.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { ReactNode } from 'react'; -import './threads.scss'; -import classNames from 'classnames'; - -export const LoadingThread = ( -
-
-
-
-
-
-
- -
-
-
-
-
-
-); - -type Props = { - hidden?: boolean; - highlighted?: boolean; - withBlock?: boolean; - className?: string; - children: ReactNode; -}; - -export default ({ hidden, highlighted, className, withBlock, children }: Props) => ( -
-
-
{children}
-
-
-); diff --git a/twake/frontend/src/app/views/applications/messages/parts/threads.scss b/twake/frontend/src/app/views/applications/messages/parts/threads.scss deleted file mode 100644 index 448944f4..00000000 --- a/twake/frontend/src/app/views/applications/messages/parts/threads.scss +++ /dev/null @@ -1,207 +0,0 @@ -.dragged { - .draggable_clone > .thread, - .draggable_clone > .thread-section { - opacity: 1; - border: 1px solid var(--grey-background); - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1); - border-radius: var(--border-radius-base); - width: 100%; - font-size: 14px; - - .message { - padding: 12px; - } - - .sender-space { - width: 28px; - } - } - .draggable_clone > .thread-section, - .draggable_clone > .thread .head-section { - .message-options.drag { - display: flex; - opacity: 1; - box-shadow: none; - } - } -} - -.drag_message { - .thread-container.has-droppable { - .thread:hover:not(.dragging_opacity) { - border: 1px solid var(--primary) !important; - box-shadow: 0 2px 4px 0 var(--primary-background) !important; - overflow: hidden; - } - } - - .message-options { - display: none; - } -} - -.thread-container, -.dragged { - .thread.dragging_opacity > *, - .thread-section.dragging_opacity > * { - opacity: 0.2; - } - - padding-top: 8px; - padding-bottom: 8px; - - &.highlighted .thread, - &.highlighted .thread.with-block { - border-radius: var(--border-radius-base); - box-shadow: 0 0 0 4px #fff892; - .thread-section .message { - border-radius: var(--border-radius-base); - } - } - opacity: 1; - transition: background-color 0.2s, box-shadow 0.2s; - min-width: 0; - - &.hidden { - opacity: 0; - } - - .thread-centerer { - margin: 0 auto; - min-width: 0; - max-width: 1000px; - width: calc(100% - 32px); - position: relative; - } - - .thread { - font-size: 14px; - margin-bottom: 8px; - - &.with-block { - border: 1px solid var(--grey-background); - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); - border-radius: var(--border-radius-base); - // in infinite list, margin are bad to compute render size because they are hard to measure - // using padding solves this issue anbd allow to scroll correctly - // cf https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around - margin-bottom: 0px; - background: var(--white); - - .message { - padding: 12px; - } - } - } - - .thread-section { - position: relative; - &.gradient { - margin-bottom: -40px; - height: 64px; - background: linear-gradient(180deg, #fff 40%, #ffffff00); - z-index: 1; - position: relative; - border: none; - .message { - background: none; - } - } - - &:not(:last-child):not(.gradient) { - border-bottom: 1px solid var(--grey-background); - } - - &.compact { - .message { - padding: 4px 12px !important; - } - } - - .message { - border-left: 4px solid transparent; - background-color: var(--white); - padding: 4px; - padding-left: 8px; - display: flex; - border-radius: var(--border-radius-base) 0 0 0; - } - - &.small-section { - .message .sender-space { - .sender-head, - .app_icon { - width: 16px; - height: 16px; - margin-top: 2px; - - .online_user_status { - position: relative; - bottom: -12px; - right: -12px; - } - } - } - } - - &.alinea { - .message { - padding-left: 32px; - } - } - - &.pinned-section { - .message { - border-left: 4px solid var(--primary); - box-shadow: -8px 0px 12px -8px var(--primary-background); - overflow: hidden; - } - } - } - - &.loading { - .sender-head, - .app_icon, - .sender-name, - .content-parent { - animation-duration: 10s; - animation-fill-mode: forwards; - animation-iteration-count: infinite; - animation-name: placeHolderShimmer; - animation-timing-function: linear; - background-repeat: repeat !important; - background-image: linear-gradient( - to right, - var(--grey-light) 0px, - #f2f2f2 120px, - var(--grey-light) 234px - ) !important; - } - .sender-name { - width: 30%; - max-width: 200px; - } - .content-parent { - width: 100%; - } - .sender-name, - .content-parent { - height: 14px; - margin: 4px 0; - display: inline-block; - border-radius: var(--border-radius-base); - } - div, - span { - font-size: 0px !important; - } - - .thread-section { - border: 0px !important; - } - - .thread-section.compact { - display: none; - } - } -} diff --git a/twake/frontend/src/app/views/applications/messages/placeholder.tsx b/twake/frontend/src/app/views/applications/messages/placeholder.tsx deleted file mode 100644 index 38e7dc41..00000000 --- a/twake/frontend/src/app/views/applications/messages/placeholder.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Skeleton } from 'antd'; -import React from 'react'; - -export const MessagesPlaceholder = () => { - return ( -
- {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => ( -
- -
- ))}{' '} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/messages/thread-messages-list.tsx b/twake/frontend/src/app/views/applications/messages/thread-messages-list.tsx deleted file mode 100644 index 105aaf0c..00000000 --- a/twake/frontend/src/app/views/applications/messages/thread-messages-list.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import ListBuilder, { ListBuilderHandle } from './list-builder'; -import TimeSeparator from './message/time-separator'; -import MessageWithReplies from './message/message-with-replies'; -import FirstThreadMessage from './message/parts/FirstMessage/FirstThreadMessage'; -import { MessagesListContext } from './messages-list'; -import { useThreadMessages } from 'app/features/messages/hooks/use-thread-messages'; -import { - MessagesAndComponentsType, - withNonMessagesComponents, -} from '../../../features/messages/hooks/with-non-messages-components'; -import { useHighlightMessage } from 'app/features/messages/hooks/use-highlight-message'; -import GoToBottom from './parts/go-to-bottom'; -import { MessagesPlaceholder } from './placeholder'; -import { cleanFrontMessagesFromListOfMessages } from 'app/features/messages/hooks/use-message-editor'; -import { getMessage } from 'app/features/messages/hooks/use-message'; - -type Props = { - companyId: string; - workspaceId: string; - channelId: string; - threadId: string; - readonly?: boolean; -}; - -export default ({ companyId, workspaceId, channelId, threadId, readonly }: Props) => { - const listBuilderRef = useRef(null); - const [atBottom, setAtBottom] = useState(true); - - const { highlight, cancelHighlight, reachedHighlight } = useHighlightMessage(); - const { - messages: _messages, - loadMore, - window, - jumpTo, - convertToKeys, - } = useThreadMessages({ - companyId, - threadId: threadId || '', - }); - const messages = withNonMessagesComponents(_messages, window.reachedStart); - - useEffect(() => { - loadMore('history'); - }, []); - - const loadMoreMessages = async ( - direction: 'history' | 'future', - limit?: number, - offsetItem?: MessagesAndComponentsType, - ) => { - const messages = await loadMore(direction, limit, offsetItem?.id); - return withNonMessagesComponents( - convertToKeys(companyId, messages), - window.reachedStart && direction === 'history', - ); - }; - - useEffect(() => { - //Manage scroll to highlight - if ( - listBuilderRef.current && - highlight && - highlight.answerId && - highlight.reachedThread && - !highlight.reachedAnswer - ) { - // Find the correct index of required message - const index = messages.findIndex(m => m.id === highlight.answerId); - if (index < 0) { - // Load the right portion of messages - jumpTo(highlight.answerId); - return; - } - setTimeout(() => { - if (listBuilderRef.current) - listBuilderRef.current.scrollToIndex({ - align: 'start', - index: index, - }); - setTimeout(() => reachedHighlight('answer'), 1000); - }, 1000); - } - }, [highlight, messages.length]); - - const jumpToBottom = () => { - if (window.reachedEnd) { - listBuilderRef.current?.scrollTo({ top: 10000000, behavior: 'smooth' }); - } else { - // Load the right portion of messages - jumpTo(''); - } - }; - - //This hide virtuoso but it start to work in backend - const virtuosoLoading = highlight && highlight.answerId && !highlight?.reachedAnswer; - - return ( - - {(!window.loaded || virtuosoLoading) && } - {!window.loaded &&
} - {window.loaded && ( - ) => { - const scrollBottom = - (e.target as HTMLElement).scrollHeight - - (e.target as HTMLElement).scrollTop - - (e.target as HTMLElement).clientHeight; - const closeToBottom = scrollBottom < 100; - if (closeToBottom !== atBottom) setAtBottom(closeToBottom); - cancelHighlight(); - }} - items={messages} - itemId={m => m.type + (getMessage(m.id)?.context?._front_id || m.id) + m.id} - emptyListComponent={} - filterOnAppend={messages => { - return cleanFrontMessagesFromListOfMessages(messages); - }} - itemContent={(_index, m) => { - if (m.type === 'timeseparator') { - return ( -
- -
- ); - } - - if (m.type === 'header') { - return ( -
- -
- ); - } - - return ( -
- -
- ); - }} - loadMore={loadMoreMessages} - /> - )} - {!(atBottom && window.reachedEnd) && window.loaded && messages.length > 0 && ( - { - jumpToBottom(); - }} - /> - )} -
- ); -}; diff --git a/twake/frontend/src/app/views/applications/tasks/board-picker/board-picker.jsx b/twake/frontend/src/app/views/applications/tasks/board-picker/board-picker.jsx deleted file mode 100755 index a3aae0d5..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board-picker/board-picker.jsx +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/jsx-key */ -import React, { Component } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import Workspaces from 'app/deprecated/workspaces/workspaces.jsx'; -import Emojione from 'components/emojione/emojione'; -import Loader from 'components/loader/loader.jsx'; - -export default class BoardPicker extends Component { - constructor() { - super(); - this.state = { - i18n: Languages, - }; - Languages.addListener(this); - - this.boards_collection_key = 'boards_picker_' + Workspaces.getCurrentUserId; - - Collections.get('boards').addListener(this); - Collections.get('boards').addSource( - { - http_base_url: 'tasks/board', - http_options: { - workspace_id: Workspaces.getCurrentUserId, - }, - websockets: [{ uri: 'boards/' + Workspaces.getCurrentUserId, options: { type: 'board' } }], - }, - this.boards_collection_key, - // eslint-disable-next-line @typescript-eslint/no-empty-function - () => {}, - ); - } - componentWillUnmount() { - Languages.removeListener(this); - Collections.get('boards').removeSource(this.boards_collection_key); - } - render() { - var boards = Collections.get('boards').findBy({ workspace_id: Workspaces.currentWorkspaceId }); - var loading = - !Collections.get('boards').did_load_first_time[this.boards_collection_key] && - boards.length == 0; - - return ( -
- {loading && ( -
- -
- )} - - {!loading && - boards.map(board => { - return ( -
{ - this.props.onChoose(board); - }} - > -
- {board.emoji && } - {board.title} -
-
- {board.active_tasks || '0'}{' '} - {Languages.t('scenes.apps.tasks.active_tasks', [], 'tâches actives')} -
-
- ); - })} -
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/Board.jsx b/twake/frontend/src/app/views/applications/tasks/board/Board.jsx deleted file mode 100755 index 5b6b66f9..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/Board.jsx +++ /dev/null @@ -1,501 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; - -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import { DragDropContext, Droppable } from 'react-beautiful-dnd'; -import List from './list/list.jsx'; -import PerfectScrollbar from 'react-perfect-scrollbar'; -import Loader from 'components/loader/loader.jsx'; -import './Board.scss'; -import Emojione from 'components/emojione/emojione'; -import Rounded from 'components/inputs/rounded.jsx'; -import Menu from 'components/menus/menu.jsx'; -import ListEditor from './list/list-editor.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import Tabs from 'components/tabs/tabs.jsx'; -import ChevronDownIcon from '@material-ui/icons/KeyboardArrowDownOutlined'; -import User from 'components/user/user.jsx'; -import MenusManager from 'app/components/menus/menus-manager.jsx'; -import RouterService from 'app/features/router/services/router-service'; - -export default class Board extends React.Component { - constructor(props) { - super(props); - this.props = props; - this.state = { - i18n: Languages, - archived: false, - }; - Languages.addListener(this); - - this.board_collection_key = 'board_' + this.props.board.id; - - this.user_mode = this.props.board.id.split('_')[0] === 'user'; - - const { channelId } = RouterService.getStateFromRoute(); - - //Lists (only in board mode, not in user mode) - if (!this.user_mode) { - Collections.get('lists').addListener(this); - Collections.get('lists').addSource( - { - http_base_url: 'tasks/list', - http_options: { - channel_id: channelId, - board_id: this.props.board.id, - }, - websockets: [{ uri: 'board_lists/' + this.props.board.id, options: { type: 'list' } }], - }, - this.board_collection_key, - ); - } - - Collections.get('tasks').addListener(this); - Collections.get('tasks').addSource( - { - http_base_url: 'tasks/task', - http_options: { - channel_id: channelId, - board_id: this.props.board.id, - }, - websockets: [{ uri: 'board_tasks/' + this.props.board.id, options: { type: 'task' } }], - }, - this.board_collection_key, - ); - } - componentWillUnmount() { - Languages.removeListener(this); - - if (!this.user_mode) { - Collections.get('lists').removeListener(this); - Collections.get('lists').removeSource(this.board_collection_key); - } - - Collections.get('tasks').removeListener(this); - Collections.get('tasks').removeSource(this.board_collection_key); - } - - shouldComponentUpdate() { - if (this.retry_update) clearTimeout(this.retry_update); - if (TasksService.paused_notify[this.props.board.id]) { - this.retry_update = setTimeout(() => { - this.setState({}); - }, 1000); - return false; - } - return true; - } - - onDragStart = () => { - TasksService.paused_notify[this.props.board.id] = true; - }; - - onDragEnd = event => { - TasksService.paused_notify[this.props.board.id] = false; - - if (!event.destination) { - return; - } - - if (event.type === 'board') { - var element_front_id = event.draggableId.split('_')[1]; - var new_index = event.destination.index; - var list = Collections.get('lists').findByFrontId(element_front_id); - if (list) { - Collections.get('lists').updateObject( - { - order: TasksService.newIndexAfter( - 'lists_' + this.props.board.id, - new_index - (new_index < event.source.index ? 1 : 0), - ), - }, - list.front_id, - ); - TasksService.setElementIndexPool( - 'lists_' + this.props.board.id, - Collections.get('lists').findBy({ board_id: this.props.board.id }), - ); - Collections.get('lists').save(list, this.board_collection_key); - } - } - - if (event.type === 'list') { - // eslint-disable-next-line no-redeclare - var element_front_id = event.draggableId.split('_')[1]; - var source_list_front_id = event.source.droppableId; - var destination_list_front_id = event.destination.droppableId; - var destination_index = event.destination.index; - - var source_list = Collections.get('lists').findByFrontId(source_list_front_id); - // eslint-disable-next-line no-redeclare - var list = Collections.get('lists').findByFrontId(destination_list_front_id); - var task = Collections.get('tasks').findByFrontId(element_front_id); - - if (task && list && list.id) { - Collections.get('tasks').updateObject( - { - list_id: list.id, - order: TasksService.newIndexAfter( - 'tasks_' + list.id, - destination_index - - (destination_index < event.source.index || - destination_list_front_id !== source_list_front_id - ? 1 - : 0), - ), - }, - task.front_id, - ); - if (source_list && source_list.id) { - TasksService.setElementIndexPool( - 'tasks_' + source_list.id, - Collections.get('tasks').findBy({ - board_id: this.props.board.id, - list_id: source_list.id, - }), - ); - } - TasksService.setElementIndexPool( - 'tasks_' + list.id, - Collections.get('tasks').findBy({ board_id: this.props.board.id, list_id: list.id }), - ); - Collections.get('tasks').save(task, this.board_collection_key); - } - } - }; - - render() { - var current_board = this.props.board; - - this.isDragDisabled = false; - - var lists = Collections.get('lists').findBy({ board_id: this.props.board.id }); - if (this.user_mode) { - lists = [ - { - id: 'allusertasks_' + this.props.board.id.split('_')[1], - title: Languages.t('components.workspace.list_manager.all', [], 'All'), - all: true, - }, - ]; - - var workspaces = []; - TasksService.getTasksInList( - this.props.board.id, - lists[0].id, - this.state.archived ? true : false, - ).forEach(task => { - if (workspaces.indexOf(task.workspace_id) < 0) { - workspaces.push(task.workspace_id); - - var workspace = Collections.get('workspaces').find(task.workspace_id); - - if (workspace) { - lists.push({ - id: - 'workspaceusertasks_' + this.props.board.id.split('_')[1] + '_' + task.workspace_id, - title: workspace.name, - other_group: true, - }); - } - } - }); - - lists.sort((a, b) => { - if (a.all) { - return -1; - } - if (a.other_group) { - return 1; - } - if (b.other_group) { - return -1; - } - return 0; - }); - - this.isDragDisabled = true; - } - - var loading = - (!this.user_mode && - !Collections.get('lists').did_load_first_time[this.board_collection_key]) || - !Collections.get('tasks').did_load_first_time[this.board_collection_key]; - - if (loading) { - return ( -
- -
- ); - } - - TasksService.setElementIndexPool( - 'lists_' + this.props.board.id, - Collections.get('lists').findBy({ board_id: this.props.board.id }), - ); - - // eslint-disable-next-line no-redeclare - var lists = lists.sort( - (a, b) => - TasksService.getElementIndex(a, 'lists_' + a.board_id) - - TasksService.getElementIndex(b, 'lists_' + b.board_id), - ); - if (!this.user_mode) { - lists.push({ - id: 'add_list', - render: ( -
- { - return ( - - ); - }, - }, - ]} - > - - -
- ), - }); - } - - return ( - -
- {!this.props.noTitle && ( -
- {!((this.props.tab || {}).configuration || {}).board_id && ( -
{ - TasksService.openBoard(null); - }} - > - {Languages.t('scenes.apps.board.all_boards', [], 'All Boards')} -
- )} - {current_board.emoji && ( - - )} - {current_board.user_image && ( - - )} - {current_board.title} -
- )} - -
- - {!this.props.hideMore && ( -
{ - MenusManager.openMenu( - [ - { - type: 'title', - text: Languages.t('scenes.apps.board.display_as', [], 'Afficher en tant que'), - }, - { - type: 'menu', - icon: current_board.view_mode === 'grid' ? 'check' : ' ', - className: current_board.view_mode === 'grid' ? 'primary' : ' ', - rightIcon: 'window-restore', - text: Languages.t('scenes.apps.board.kanban', [], 'Kanban'), - onClick: () => { - current_board.view_mode = 'grid'; - Collections.get('boards').save( - current_board, - this.props.boardsCollectionKey, - ); - }, - }, - { - type: 'menu', - icon: current_board.view_mode === 'list' ? 'check' : ' ', - className: current_board.view_mode === 'list' ? 'primary' : ' ', - rightIcon: 'list-ul', - text: Languages.t('scenes.apps.calendar.calendar.list_btn', [], 'Liste'), - onClick: () => { - current_board.view_mode = 'list'; - Collections.get('boards').save( - current_board, - this.props.boardsCollectionKey, - ); - }, - }, - - { type: 'separator' }, - { - type: 'menu', - icon: this.state.archived === false ? 'check' : ' ', - className: this.state.archived === false ? 'primary' : ' ', - text: Languages.t('scenes.apps.board.active_tasks', [], 'Tâches actives'), - onClick: () => { - this.setState({ archived: false }); - }, - }, - { - type: 'menu', - icon: this.state.archived ? 'check' : ' ', - className: this.state.archived ? 'primary' : ' ', - text: Languages.t( - 'scenes.apps.board.archived_tasks', - [ - Collections.get('tasks').findBy({ - archived: true, - board_id: current_board.id, - }).length, - ], - 'Tâches archivées ($1)', - ), - onClick: () => { - this.setState({ archived: true }); - }, - }, - ], - { x: evt.clientX, y: evt.clientY }, - 'bottom', - ); - }} - > - {Languages.t('general.more', [], 'Plus')} - -
- )} -
- -
-
- {this.props.mode === 'list' && ( - { - return { - id: item.front_id || item.id, - titleClassName: item.id === 'add_list' ? 'no-selection-border' : '', - titleStyle: { borderBottomColor: item.color }, - title: () => { - if (item.id === 'add_list') { - return item.render; - } - return ( - (item.title || '-') + - ' (' + - TasksService.getTasksInList( - item.board_id, - item.id, - this.state.archived ? true : false, - ).length + - ')' - ); - }, - render: () => { - if (item.id === 'add_list') { - return ''; - } - return ( - - ); - }, - }; - }) - .reduce((acc, cur) => { - acc[cur.id] = cur; - return acc; - }, {})} - /> - )} - - {this.props.mode === 'grid' && ( - - - {provided => ( -
- {lists - .filter(a => a.id !== 'add_list') - .map(item => { - return ( - - ); - })} - - {provided.placeholder} - - {lists[lists.length - 1].render} -
- )} -
-
- )} -
-
- - {/*{ - MediumPopupManager.open(, {size: {width: 600}}); - }} />*/} - - ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/Board.scss b/twake/frontend/src/app/views/applications/tasks/board/Board.scss deleted file mode 100755 index c79d5395..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/Board.scss +++ /dev/null @@ -1,233 +0,0 @@ -.board { - display: flex; - flex-direction: column; - height: 100%; - flex: 1; - - margin: 0 -32px; - margin-bottom: -15px; - margin-top: -8px; - - &.inline { - height: auto; - .list, - .component_tabs, - .component_tabs > .body, - .lists_before { - display: block !important; - height: auto !important; - position: relative !important; - max-height: none !important; - } - .list .list_draggable .droppable { - max-height: none; - height: auto; - flex: none; - } - } - - &.mode_list { - .lists_before { - padding-top: 16px; - } - .lists_before .component_tabs .body .scrollable_task_list { - padding: 0px 24px; - } - .lists_before .component_tabs .component_tabs_tabs, - .lists_before .component_tabs .body .add_task { - height: auto; - margin-right: 24px; - margin-left: 24px; - } - .component_tabs { - height: 100%; - .body { - display: flex; - flex-direction: column; - } - } - .component_tabs > .body { - padding-left: 0px; - padding-right: 0px; - & > .component_tabs_tab { - &.no-selection-border { - border-bottom: 0px; - margin-left: 0px; - padding-left: 0px; - } - } - } - .list .list_draggable .list_header { - display: none; - } - .list { - position: absolute; - height: 100%; - width: 100%; - } - .list, - .list_draggable { - margin-right: 0px; - width: 100%; - .task_draggable_parent { - margin-bottom: 0px; - .task { - border-left: 0px; - border-right: 0px; - border-top: 0px; - border-radius: 0px; - display: flex; - padding-right: 56px; - - .task_options { - padding-top: 2px; - display: block; - background: transparent; - } - - .task_line_1 { - padding-top: 2px; - flex: 1; - .task_title { - line-height: 22px; - font-size: 14px; - font-weight: bold; - } - .tags-in-name { - display: inline-block; - vertical-align: bottom; - } - } - .task_line_2, - .task_line_tags, - .task_users { - margin-top: 0px; - width: 20%; - max-width: 110px; - } - .task_line_tags { - display: none; - } - } - } - } - .task { - .attachmentPicker { - width: auto; - margin: 0px; - .attachments .attachment { - width: 40px; - height: 40px; - margin-top: -8px; - margin-bottom: -8px; - margin-right: 8px; - } - .attachments, - .attachments .attachment > .file, - .attachments .attachment > .file .preview { - width: 100%; - height: 100%; - margin: 0px; - } - .attachments .attachment > .file .data { - display: none; - } - } - } - } - - .lists_before { - flex: 1; - position: relative; - overflow: hidden; - - .lists_scrollable { - white-space: nowrap; - height: 100%; - position: absolute; - width: 100%; - - & > div:first-child { - padding-left: 32px; - padding-bottom: 16px; - box-sizing: border-box; - padding-top: 16px; - } - } - } - - .list_add { - font-size: 14px; - font-weight: 500; - width: auto; - padding: 4px; - height: 32px; - padding-right: 4px; - padding-left: 8px; - line-height: 24px; - vertical-align: top; - opacity: 0.8; - - &:hover { - opacity: 1; - } - .m-icon-small { - vertical-align: middle; - margin-top: -2px; - font-size: 18px !important; - } - } -} - -.board_header { - display: flex; - - .app_title { - flex: 1; - } - - .options { - font-size: 13px; - margin-left: 10px; - line-height: 18px; - display: flex; - align-items: center; - } - - .view_title { - font-size: 24px; - margin-bottom: 0; - padding-bottom: 0; - line-height: 24px; - height: 50px; - display: flex; - - .name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - .filter_input { - flex: 1; - margin-left: 20px; - margin-top: -2px; - margin-right: 20px; - text-align: right; - } - - .anticon { - margin-right: 5px; - line-height: 30px; - } - - .board_parameters { - float: right; - cursor: pointer; - color: #666; - &:hover { - opacity: 0.5; - } - } - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/BoardEditor.jsx b/twake/frontend/src/app/views/applications/tasks/board/BoardEditor.jsx deleted file mode 100755 index e48fc57f..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/BoardEditor.jsx +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/no-direct-mutation-state */ -import React, { Component } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import MenuManager from 'app/components/menus/menus-manager.jsx'; -import Workspaces from 'app/deprecated/workspaces/workspaces.jsx'; - -import InputWithIcon from 'components/inputs/input-with-icon'; -import Button from 'components/buttons/button.jsx'; - -export default class BoardEditor extends Component { - constructor() { - super(); - this.state = { - i18n: Languages, - name: '', - emoji: '', - }; - Languages.addListener(this); - } - UNSAFE_componentWillMount() { - if (this.props.id) { - var board = Collections.get('boards').find(this.props.id); - if (board) { - this.state.name = board.title; - this.state.emoji = board.emoji; - } - } - } - componentWillUnmount() { - Languages.removeListener(this); - } - save() { - var board = {}; - if (this.props.id) { - board = Collections.get('boards').find(this.props.id); - } else { - board = Collections.get('boards').editCopy({}); - board.workspace_id = Workspaces.currentWorkspaceId; - } - board.title = this.state.name; - board.emoji = this.state.emoji; - Collections.get('boards').save(board, this.props.collectionKey); - MenuManager.closeMenu(); - } - render() { - return ( -
-
- this.save()} - onChange={value => { - this.setState({ emoji: value[0], name: value[1] }); - }} - /> -
-
-
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/list/add-task.jsx b/twake/frontend/src/app/views/applications/tasks/board/list/add-task.jsx deleted file mode 100755 index e9c64221..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/list/add-task.jsx +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from 'react'; - -import Languages from 'app/features/global/services/languages-service'; -import Input from 'components/inputs/input.jsx'; -import Button from 'components/buttons/button.jsx'; -import OutsideClickHandler from 'react-outside-click-handler'; - -export default class AddTask extends Component { - constructor(props) { - super(props); - this.state = { - i18n: Languages, - unselected: true, - }; - Languages.addListener(this); - } - componentWillUnmount() { - Languages.removeListener(this); - } - newTask() { - this.setState({ new_task_title: '' }); - if (!(this.state.new_task_title || '').trim()) { - return; - } - - var task = { - title: this.state.new_task_title, - }; - - this.props.onSubmit(task); - } - render() { - if (this.state.unselected) { - return ( -
{ - this.setState({ unselected: false }); - }} - > - {Languages.t('scenes.apps.board.new_task', [], '+ New task')} -
- ); - } - - return ( - { - this.setState({ unselected: true }); - }} - > -
- { - this.setState({ new_task_title: evt.target.value }); - }} - onEnter={() => { - this.newTask(); - }} - /> -
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/list/list-editor.jsx b/twake/frontend/src/app/views/applications/tasks/board/list/list-editor.jsx deleted file mode 100755 index cf3c7edc..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/list/list-editor.jsx +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/no-direct-mutation-state */ -import React, { Component } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import MenuManager from 'app/components/menus/menus-manager.jsx'; - -import InputWithColor from 'components/inputs/input-with-color.jsx'; -import Button from 'components/buttons/button.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; - -export default class ListEditor extends Component { - constructor() { - super(); - this.state = { - i18n: Languages, - name: '', - emoji: '', - }; - Languages.addListener(this); - } - UNSAFE_componentWillMount() { - if (this.props.id) { - var list = Collections.get('lists').find(this.props.id); - if (list) { - this.state.name = list.title; - this.state.color = list.color; - } - } - } - componentWillUnmount() { - Languages.removeListener(this); - } - save() { - var list = {}; - if (this.props.id) { - list = Collections.get('lists').find(this.props.id); - } else { - list = Collections.get('lists').editCopy({}); - list.board_id = this.props.board.id; - list.order = TasksService.newIndexAfter('lists_' + this.props.board.id); - } - list.title = this.state.name; - list.color = this.state.color; - Collections.get('lists').save(list, this.props.collectionKey); - MenuManager.closeMenu(); - } - render() { - return ( -
-
- this.save()} - onChange={value => { - this.setState({ color: value[0], name: value[1] }); - }} - /> -
-
-
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/list/list.jsx b/twake/frontend/src/app/views/applications/tasks/board/list/list.jsx deleted file mode 100755 index 568b51a6..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/list/list.jsx +++ /dev/null @@ -1,280 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; - -import Languages from 'app/features/global/services/languages-service'; -import { Draggable, Droppable } from 'react-beautiful-dnd'; -import Task from '../task/Task.jsx'; -import Icon from 'app/components/icon/icon.jsx'; -import MenusManager from 'app/components/menus/menus-manager.jsx'; -import ListEditor from './list-editor.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import AlertManager from 'app/features/global/services/alert-manager-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service'; -import AddTask from './add-task.jsx'; -import UserListManager from 'components/user-list-manager/user-list-manager'; -import PerfectScrollbar from 'react-perfect-scrollbar'; - -import './list.scss'; -import '../task/Task.scss'; - -export default class List extends React.Component { - constructor(props) { - super(props); - this.state = { - i18n: Languages, - }; - Languages.addListener(this); - Collections.get('lists').addListener(this); - Collections.get('tasks').addListener(this); - Collections.get('lists').listenOnly(this, [props.list.front_id]); - - this.did_fade_in = false; - } - componentWillUnmount() { - Languages.removeListener(this); - Collections.get('lists').removeListener(this); - Collections.get('tasks').removeListener(this); - } - listOptions() { - var list = this.props.list; - var menu = []; - menu = [ - { - text: Languages.t('general.edit'), - submenu_replace: true, - submenu: [ - { - type: 'title', - text: Languages.t('scenes.apps.tasks.list_modal.modify_list', [], 'Edit list'), - }, - { - type: 'react-element', - reactElement: level => { - return ( - - ); - }, - }, - ], - }, - ]; - - if (!WorkspaceUserRights.isInvite()) { - if (WorkspaceUserRights.hasWorkspacePrivilege()) { - menu.push( - { - text: Languages.t( - 'scenes.apps.tasks.list_modal.predefined_participants', - [], - 'Participants prédéfinis...', - ), - submenu_replace: true, - submenu: [ - { - type: 'title', - text: Languages.t( - 'scenes.apps.tasks.list_modal.predefined_participants', - [], - 'Participants prédéfinis', - ), - }, - { - type: 'react-element', - reactElement: () => { - return ( -
- { - return { id: id }; - })} - disableExterne={true} - scope="workspace" - onChange={ids => { - list.auto_participants = ids; - Collections.get('lists').save(list, this.props.collectionKey); - MenusManager.closeMenu(); - }} - onCancel={() => { - MenusManager.closeMenu(); - }} - /> -
- ); - }, - }, - ], - }, - - /*{type: "separator"}, - {text: "Déplacer les tâches vers...", submenu: []},*/ - - { type: 'separator' }, - { - text: Languages.t( - 'scenes.apps.tasks.list_modal.archive_all_tasks', - [], - 'Archiver toutes les tâches', - ), - hide: this.props.showArchived, - onClick: () => { - TasksService.archiveAllTasksInList(list, this.props.collectionKey); - }, - }, - { - text: Languages.t( - 'scenes.apps.tasks.list_modal.remove_archived_tasks', - [TasksService.getTasksInList(list.board_id, list.id, true).length], - 'Supprimer les archivées ($1)', - ), - className: 'error', - onClick: () => { - TasksService.removeAllTasksInList(list, true, this.props.collectionKey); - }, - }, - { - text: Languages.t('scenes.apps.tasks.list_modal.remove'), - hide: this.props.showArchived, - className: 'error', - onClick: () => { - AlertManager.confirm(() => { - Collections.get('lists').remove(list, this.props.collectionKey); - }); - }, - }, - ); - } - } - var elementRect = window.getBoundingClientRect(this.options_dom); - elementRect.x = elementRect.x || elementRect.left; - elementRect.y = elementRect.y || elementRect.top; - MenusManager.openMenu(menu, elementRect, 'right'); - } - - shouldComponentUpdate() { - if (this.retry_update) clearTimeout(this.retry_update); - if (TasksService.paused_notify[this.props.board.id]) { - this.retry_update = setTimeout(() => { - this.setState({}); - }, 1000); - return false; - } - - this.did_fade_in = true; - return true; - } - - newTask(task) { - var list = Collections.get('tasks').editCopy({}); - list.title = task.title; - list.archived = false; - list.board_id = this.props.board.id; - list.list_id = this.props.list.id; - list.order = TasksService.newIndexAfter('tasks_' + this.props.list.id, -1); - Collections.get('tasks').save(list, this.props.collectionKey); - } - - render() { - var list = this.props.list; - - var tasks = TasksService.getTasksInList( - list.board_id, - list.id, - this.props.showArchived ? true : false, - ); - TasksService.setElementIndexPool( - 'tasks_' + list.id, - Collections.get('tasks').findBy({ board_id: this.props.board.id, list_id: list.id }), - ); - - return ( -
- - {(provided, snapshot) => ( -
-
-
- - - {list.title || '-'} - - - { - this.options_dom = node; - }} - className="options" - type="ellipsis-h" - onClick={evt => this.listOptions(evt)} - /> -
- - {this.props.canCreate && this.newTask(task)} />} - - - {(provided, snapshot) => ( -
- - {(tasks || []) - .sort( - (a, b) => - TasksService.getElementIndex(a, 'tasks_' + a.list_id) - - TasksService.getElementIndex(b, 'tasks_' + b.list_id), - ) - .map((item, index) => ( - - ))} - - {(tasks || []).length == 0 && ( - - {this.state.i18n.t('scenes.apps.tasks.no_tasks')} - - )} - - {(tasks || []).length > 0 && provided.placeholder} - -
- )} -
-
-
- )} -
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/list/list.scss b/twake/frontend/src/app/views/applications/tasks/board/list/list.scss deleted file mode 100755 index 66576b26..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/list/list.scss +++ /dev/null @@ -1,181 +0,0 @@ -.board .list { - display: inline-block; - height: 100%; - vertical-align: top; - position: relative; - - .list_draggable_parent { - height: 100%; - overflow: visible; - display: flex; - align-items: stretch; - } - - .scrollable_task_list { - max-height: 100%; - overflow: scroll; - flex: 1; - position: relative; - } - - .list_draggable { - width: 280px; - max-height: 100%; - overflow: hidden; - position: relative; - display: flex; - flex-direction: column; - border-radius: var(--border-radius-base); - margin-right: 16px; - transition: box-shadow 0.2s, transform 0.2s !important; - - &.isDragging:active { - transform: rotateZ(3deg); - } - - &:hover { - .list_header .options { - display: block; - } - } - - .list_header { - width: 100%; - display: block; - box-sizing: border-box; - margin-bottom: 0; - position: relative; - - .list_title { - padding: 4px 8px; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 60%; - display: inline-block; - line-height: 24px; - - .list_title_background { - position: absolute; - width: 100%; - height: 100%; - opacity: 0.15; - top: 0; - left: 0; - border-radius: var(--border-radius-large); - } - } - - .list_color { - width: calc(100% + 2px); - height: 3px; - border-radius: var(--border-radius-base); - top: -1px; - position: absolute; - left: -1px; - } - - img.emojione { - margin-right: 6px; - margin-top: -4px; - } - - .options { - display: none; - float: right; - margin: 0px; - color: var(--black); - font-size: 20px; - margin-top: 4px; - line-height: normal; - cursor: pointer; - - &:hover { - opacity: 0.8; - } - } - } - - .add_task { - margin-bottom: 8px; - padding-top: 0px; - display: flex; - transition: opacity 0.2s, height 0.2s; - border-radius: var(--border-radius-large); - background: var(--grey-background); - padding-right: 4px; - &:hover { - background: var(--primary-background); - } - - button { - margin-top: 4px; - } - - input { - flex: 1; - margin-right: 8px; - } - - &.unselected { - &:hover { - background: var(--grey-background); - color: var(--black); - } - background: transparent; - border-radius: var(--border-radius-base); - font-size: 14px; - line-height: 32px; - padding: 0 8px; - color: var(--grey-dark); - cursor: pointer; - } - } - - .droppable { - flex: 1; - transition: padding-bottom 0.2s; - max-height: calc(100% - 80px); - height: auto; - - .empty { - height: 70px; - padding: 16px; - padding-top: 20px; - box-sizing: border-box; - font-size: 12px; - border-radius: 2px; - text-align: center; - color: var(--grey-dark); - display: inline-block; - width: 100%; - transition: all 0.2s; - } - } - } -} - -.list.done { - margin: 0; - background: #eee; - padding: 0 10px; - - .list_header { - border-top: 0px; - } - - .list_header { - margin-bottom: 10px; - } - - .task { - opacity: 0.5; - transition: all 0.2s; - - &:hover, - &.selected { - opacity: 1; - } - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/Modal.scss b/twake/frontend/src/app/views/applications/tasks/board/task/Modal.scss deleted file mode 100755 index d80d45db..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/Modal.scss +++ /dev/null @@ -1,135 +0,0 @@ -.taskModal { - .title { - font-size: 22px; - margin-bottom: 8px; - .text { - margin-top: 8px; - font-size: 12px; - font-weight: normal; - color: var(--grey-dark); - } - } - - .date.subtitle, - .location.subtitle, - .reminders.subtitle { - color: var(--grey-dark); - } - - .date_and_type { - display: flex; - .select { - min-width: 25%; - flex: 1; - max-width: 140px; - margin-right: 8px; - } - .date_selector_full { - & > div { - vertical-align: top; - } - } - } - - .participants > .menu-list { - padding: 0; - margin: 0 -16px; - width: calc(100% + 32px); - } - - .task_type { - display: inline-block; - height: 32px; - padding: 0 8px; - margin-right: 8px; - line-height: 32px; - vertical-align: middle; - background: var(--white); - border-radius: var(--border-radius-base); - border: 1px solid var(--grey-background); - box-sizing: border-box; - font-size: 14px; - padding-left: 4px; - .icon-unicon { - color: var(--grey-dark); - } - } - - .checklist { - .progress_bar_container { - display: flex; - margin-bottom: 8px; - span { - width: 40px; - font-size: 12px; - color: var(--grey-dark); - } - .progress_bar { - background: var(--grey-background); - border-radius: var(--border-radius-base); - height: 8px; - flex: 1; - overflow: hidden; - position: relative; - top: 4px; - .progress { - background: var(--primary); - position: absolute; - top: 0; - left: 0; - height: 8px; - transition: width 0.2s; - } - } - } - - .checklist_element_editor { - flex: 1; - margin-top: -4px; - } - - .checklist-row { - min-height: 24px; - margin-bottom: 0px; - margin-top: 0px; - display: flex; - font-size: 13px; - - .edit-task.icon-unicon { - opacity: 0; - font-size: 14px; - line-height: normal !important; - top: 2px; - position: relative; - margin-left: 4px !important; - } - &:hover { - .edit-task { - opacity: 1; - } - } - .checkbox { - top: 0px; - position: relative; - margin-right: 8px; - } - span { - top: 2px; - position: relative; - cursor: pointer; - } - .trash.icon-unicon, - .edit-task.icon-unicon { - cursor: pointer; - line-height: 24px; - margin-left: 8px; - &:hover { - color: var(--primary); - &.trash { - color: var(--red); - } - } - } - } - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/Task.jsx b/twake/frontend/src/app/views/applications/tasks/board/task/Task.jsx deleted file mode 100755 index ad88c3fa..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/Task.jsx +++ /dev/null @@ -1,168 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from 'react'; - -import Languages from 'app/features/global/services/languages-service'; - -import { Draggable } from 'react-beautiful-dnd'; - -import './Task.scss'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import TaskDetails from './TaskDetails.jsx'; -import MediumPopupManager from 'app/components/modal/modal-manager'; -import Icon from 'app/components/icon/icon.jsx'; -import UserListManager from 'components/user-list-manager/user-list-manager'; -import TagPicker from 'components/tag-picker/tag-picker.jsx'; -import AttachmentPicker from 'components/attachment-picker/attachment-picker.jsx'; - -export default class Task extends Component { - constructor(props) { - super(props); - this.state = { - i18n: Languages, - }; - Languages.addListener(this); - Collections.get('tasks').addListener(this); - Collections.get('tasks').listenOnly(this, [props.task.front_id]); - } - componentWillUnmount() { - Languages.removeListener(this); - Collections.get('tasks').removeListener(this); - } - - render() { - var task = this.props.task; - - if (!task) { - return ''; - } - - return ( - - {(provided, snapshot) => ( -
-
{ - MediumPopupManager.open( - , - { size: { width: 600 } }, - ); - }} - className={ - 'task task_draggable noselect ' + (snapshot.isDragging ? 'isDragging ' : '') - } - > -
-
- {task.title} -    - -
-
- -
-
- - {!!task.attachments && task.attachments.length > 0 && ( - - )} - - {!!( - task.description || - (task.attachments && task.attachments.length) || - (!!task.checklist && task.checklist.length > 0) - ) && ( -
- {!!task.description && - task.description.original_str && - task.description.original_str.trim() && ( -
- -
- )} - {!!task.attachments && task.attachments.length > 0 && ( -
- -
- )} - {!!task.start && task.start > 0 && ( -
- -
- )} - {!!task.before && task.before > 0 && ( -
- -
- )} - {!!task.checklist && task.checklist.length > 0 && ( -
- - - {' '} - {parseInt( - (100 * - task.checklist.map(e => (e.value ? 1 : 0)).reduce((a, b) => a + b)) / - task.checklist.length, - )} - % - -
- )} -
- )} - -
- -
- - {!!task.participants && task.participants.length > 0 && ( -
- { - return { id: participant.user_id_or_mail }; - })} - readOnly - collapsed - medium - /> -
- )} -
-
-
- )} - - ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/Task.scss b/twake/frontend/src/app/views/applications/tasks/board/task/Task.scss deleted file mode 100755 index a127f990..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/Task.scss +++ /dev/null @@ -1,150 +0,0 @@ -.task_draggable_parent { - margin-bottom: 8px; -} - -.task_draggable_parent.archived { - opacity: 0.5; -} - -.task_draggable { - white-space: pre-wrap; - padding: 16px; - border: 1px solid var(--grey-background); - border-radius: var(--border-radius-base); - font-size: 14px; - transition: box-shadow 0.2s, transform 0.2s; - background: var(--white); - - &.isDragging:active { - box-shadow: 0 0 16px rgba(0, 0, 0, 0.2); - border: 1px solid var(--white); - border-radius: var(--border-radius-base); - transform: rotateZ(3deg); - } - - .attachmentPicker { - margin-top: 8px; - width: 100%; - .attachments, - .attachments .attachment, - .attachments .attachment > .file, - .attachments .attachment > .file .preview { - width: 100%; - margin: 0px; - } - } - - .task.ant-card { - .ant-card-body { - padding: 14px; - } - - .addon_icon { - float: right; - margin-left: 5px; - - .anticon { - line-height: 20px; - } - } - } - - .task.selected { - border-color: #0487e7; - } -} - -.task { - position: relative; - - .tags-in-name { - display: none; - } - - .task_options { - display: none; - position: absolute; - top: 8px; - right: 8px; - height: 24px; - background: var(--grey-background); - border-radius: var(--border-radius-base); - line-height: 24px; - cursor: pointer; - &:hover { - background: var(--primary-background); - } - } - &:hover .task_options { - display: block; - } - - .task_line_2 { - float: left; - vertical-align: top; - padding: 5px 0; - padding-bottom: 0; - height: 18px; - margin-top: 8px; - } - - .task_line_tags { - margin-bottom: -8px; - margin-top: 8px; - } - - .task_users { - float: right; - vertical-align: top; - height: 26px; - margin-top: 8px; - } - - .task_info { - display: inline; - color: var(--black); - line-height: 16px; - font-weight: 500; - font-size: 12px; - vertical-align: top; - margin-right: 4px; - i { - color: var(--grey-dark); - &:before { - margin-left: 0px; - } - } - span, - i { - vertical-align: top; - line-height: 18px; - } - } - - .text { - font-size: 14px; - } - - .isDone { - display: none; - position: absolute; - bottom: 14px; - right: 14px; - font-size: 14px; - border: solid 1px rgba(0, 0, 0, 0.6); - border-radius: 50%; - padding: 2px; - background: #fff; - box-shadow: 0 0 10px 10px rgba(255, 255, 255, 1); - } - .isDone:hover { - cursor: pointer; - color: rgb(81, 183, 91); - border-color: rgb(81, 183, 91); - } -} -.task:hover { - .isDone { - display: block; - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/TaskDetails.jsx b/twake/frontend/src/app/views/applications/tasks/board/task/TaskDetails.jsx deleted file mode 100755 index 999a269c..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/TaskDetails.jsx +++ /dev/null @@ -1,240 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import UserListManager from 'components/user-list-manager/user-list-manager'; -import Button from 'components/buttons/button.jsx'; -import Menu from 'components/menus/menu.jsx'; -import TagPicker from 'components/tag-picker/tag-picker.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import TaskEditor from './TaskEditor.jsx'; -import MediumPopupManager from 'app/components/modal/modal-manager'; -import Checklist from './parts/Checklist.jsx'; -import { - ObjectModal, - ObjectModalFormTitle, - ObjectModalTitle, -} from 'components/object-modal/deprecated-object-modal.jsx'; -import Twacode from 'components/twacode/twacode'; -import AttachmentPicker from 'components/attachment-picker/attachment-picker.jsx'; -import moment from 'moment'; -import './Modal.scss'; - -export default class TaskDetails extends Component { - constructor() { - super(); - this.state = { - i18n: Languages, - }; - Languages.addListener(this); - } - componentWillUnmount() { - this.update_timeout && clearTimeout(this.update_timeout); - Languages.removeListener(this); - } - remove() { - TasksService.remove(this.props.task, this.props.collectionKey); - } - archive() { - TasksService.archive(this.props.task, this.props.collectionKey); - } - unarchive() { - TasksService.unarchive(this.props.task, this.props.collectionKey); - } - changeTags(tags) { - this.props.task.tags = tags; - Collections.get('tasks').updateObject(this.props.task); - this.setState({}); - this.changeTask(); - } - changeParticipants(pa) { - this.props.task.participants = pa; - Collections.get('tasks').updateObject(this.props.task); - this.changeTask(); - } - changeChecklist(cl) { - this.props.task.checklist = cl; - Collections.get('tasks').updateObject(this.props.task); - this.changeTask(); - } - changeTask(notify) { - if (notify || notify === undefined) { - this.update_timeout && clearTimeout(this.update_timeout); - this.update_timeout = setTimeout(() => { - Collections.get('tasks').save(this.props.task, this.props.collectionKey); - }, 1000); - this.setState({}); - } - } - edit() { - var task = this.props.task; - TasksService.edited = Collections.get('tasks').editCopy(task); - MediumPopupManager.open( - { - console.log(task); - }} - />, - { size: { width: 600 } }, - ); - } - render() { - var task = this.props.task; - var readonly = false; - - var list = Collections.get('lists').find(task.list_id) || {}; - - return ( - MediumPopupManager.closeAll()} - onEdit={ - readonly - ? false - : () => { - this.edit(); - } - } - footer={ -
- {!readonly && ( -
- - - - {!task.archived && ( - - )} -
- )} -
- } - title={ -
- {task.title || ''} - {list.title && ( -
- {Languages.t('scenes.apps.tasks.board.tasks.in_list', [], 'In list')}{' '} - {list.title} -
- )} -
- } - > - { - this.changeTags(values); - }} - /> - - {!!task.description && - task.description.original_str && - task.description.original_str.trim() && ( -
- - -
- )} - - - { - this.changeChecklist(val); - }} - /> - - - { - return { id: participant.user_id_or_mail }; - })} - scope="workspace" - onUpdate={ids_mails => { - this.changeParticipants( - ids_mails.map(id => { - return { user_id_or_mail: id }; - }), - ); - Menu.closeAll(); - }} - /> - - - - - - - {!!task.start && task.start > 0 && ( - - {Languages.t('scenes.apps.tasks.board.starts', [], 'Démarre le')}{' '} - {moment(new Date(task.start * 1000)).format('L LT')}.{' '} - - )} -
- {!!task.before && task.before > 0 && ( - - {Languages.t('scenes.apps.tasks.board.ends', [], 'À terminer avant le')}{' '} - {moment(new Date(task.before * 1000)).format('L LT')}.{' '} - - )} -
- -
-
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/TaskEditor.jsx b/twake/frontend/src/app/views/applications/tasks/board/task/TaskEditor.jsx deleted file mode 100755 index e51262d2..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/TaskEditor.jsx +++ /dev/null @@ -1,259 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import Button from 'components/buttons/button.jsx'; -import ReminderSelector from 'components/reminder-selector/reminder-selector.jsx'; -import UserListManager from 'components/user-list-manager/user-list-manager'; -import Menu from 'components/menus/menu.jsx'; -import Input from 'components/inputs/input.jsx'; -import DateSelectorInput from 'components/calendar/date-picker.jsx'; -import TimeSelector from 'components/calendar/time-selector.jsx'; -import Checkbox from 'app/components/inputs/deprecated_checkbox.jsx'; -import MediumPopupManager from 'app/components/modal/modal-manager'; -import Checklist from './parts/Checklist.jsx'; -import TagPicker from 'components/tag-picker/tag-picker.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import { - ObjectModal, - ObjectModalFormTitle, -} from 'components/object-modal/deprecated-object-modal.jsx'; -import PseudoMarkdownCompiler from 'app/features/global/services/pseudo-markdown-compiler-service'; -import AttachmentPicker from 'components/attachment-picker/attachment-picker.jsx'; - -import './Modal.scss'; - -export default class TaskEditor extends React.Component { - constructor(props) { - super(props); - this.state = { - i18n: Languages, - }; - Languages.addListener(this); - Collections.get('tasks').addListener(this); - // Collections.get("tasks").listenOnly(this, [props.task.front_id]); - } - UNSAFE_componentWillMount() { - TasksService.preview = null; - } - componentWillUnmount() { - Languages.removeListener(this); - Collections.get('tasks').removeListener(this); - } - remove() { - TasksService.remove(this.props.task, this.props.collectionKey); - } - archive() { - TasksService.archive(this.props.task, this.props.collectionKey); - } - unarchive() { - TasksService.unarchive(this.props.task, this.props.collectionKey); - } - change(key, value) { - this.props.task[key] = value; - Collections.get('tasks').notify(); - } - render() { - var task = this.props.task; - task.description = task.description || ''; - var description = task.description; - - if ((description || {}).original_str === '') { - description = ''; - } else if (typeof description == 'object') { - description = PseudoMarkdownCompiler.compileToText(description); - } - - return ( - MediumPopupManager.closeAll()} - footer={ -
- - - -
- } - > - - { - this.change('title', evt.target.value); - }} - className="full_width" - big - /> - -
- { - this.change('tags', values, true); - }} - /> -
- - - { - this.change('description', evt.target.value); - }} - className="full_width" - /> - - - { - this.change('checklist', val); - }} - /> - - - { - return { id: participant.user_id_or_mail }; - })} - scope="workspace" - onUpdate={ids_mails => { - this.change( - 'participants', - ids_mails.map(id => { - return { user_id_or_mail: id }; - }), - ); - Menu.closeAll(); - }} - /> - - - this.change('attachments', attachments)} - /> - - - - 0} - onChange={v => { - this.change('before', v ? new Date().setHours(10, 0, 0) / 1000 + 60 * 60 * 24 * 7 : 0); - }} - className="small" - label={Languages.t('scenes.apps.tasks.board.tasks.use_deadline', [], 'Use deadline')} - /> -
- {!!task.before && task.before > 0 && ( -
- this.change('before', value)} - className="small bottom-margin right-margin" - /> - this.change('before', value)} - className="small bottom-margin" - /> -
- )} - {!!task.before && task.before > 0 && ( - this.change('notifications', reminders)} - /> - )} - -
- 0} - onChange={v => { - this.change('start', v ? new Date().setHours(10, 0, 0) / 1000 + 60 * 60 * 24 : 0); - }} - className="small" - label={Languages.t('scenes.apps.tasks.board.tasks.use_starttime', [], 'Use start time')} - /> -
- {!!task.start && task.start > 0 && ( -
- this.change('start', value)} - className="small bottom-margin right-margin" - /> - this.change('start', value)} - className="small bottom-margin" - /> -
- )} - -
-
-
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/TaskElement.jsx b/twake/frontend/src/app/views/applications/tasks/board/task/TaskElement.jsx deleted file mode 100755 index 5f99ab94..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/TaskElement.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { Component } from 'react'; -import Icon from 'app/components/icon/icon.jsx'; -import './Task.scss'; - -export default class TaskElement extends React.Component { - constructor() { - super(); - - this.state = {}; - } - render() { - var task = this.props.task; - - if (!task) { - return ''; - } - return ( -
- {task.name} - - {task.like != 0 && ( -
- {task.like} -
- )} - {task.weight > 1 && ( -
- {task.weight} -
- )} - {task.to > 0 && ( -
- -
- )} - {task.from > 0 && ( -
- -
- )} - {task.description && ( -
- -
- )} - {this.state.boardService && task.list != this.state.boardService.doneId && ( -
- this.state.boardService.moveTask(task.id, this.state.boardService.doneId, 0) - } - > - -
- )} -
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/board/task/parts/Checklist.jsx b/twake/frontend/src/app/views/applications/tasks/board/task/parts/Checklist.jsx deleted file mode 100755 index ba6de1a2..00000000 --- a/twake/frontend/src/app/views/applications/tasks/board/task/parts/Checklist.jsx +++ /dev/null @@ -1,122 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import Icon from 'app/components/icon/icon.jsx'; -import Checkbox from 'app/components/inputs/deprecated_checkbox.jsx'; -import InputEnter from 'components/inputs/input-enter.jsx'; -import Button from 'components/buttons/button.jsx'; -import Languages from 'app/features/global/services/languages-service'; - - -export default class Checklist extends React.Component { - constructor() { - super(); - this.state = { - checklist_edit: {}, - }; - } - onChange(cl) { - this.props.onChange && this.props.onChange(cl); - this.setState({}); - } - render() { - var checklist = this.props.value || []; - var progress = - checklist.length > 0 - ? parseInt( - (100 * checklist.map(e => (e.value ? 1 : 0)).reduce((a, b) => a + b)) / - checklist.length, - ) - : 0; - - return ( -
- {checklist.length > 0 && ( -
- {progress}% -
-
-
-
- )} - - { - (checklist || []).map((item, i) => { - if (item) { - return ( -
-
- { - item.value = v; - this.onChange(checklist); - }} - /> - {!(this.state.checklist_edit === i) && {item.text}} - {this.state.checklist_edit === i && !this.props.readOnly && ( -
- { - item.text = v.target.value; - this.setState({}); - }} - onEnter={() => { - this.setState({ checklist_edit: -1 }); - this.onChange(checklist); - }} - /> -
- )} - {!this.props.readOnly && ( - { - this.setState({ - checklist_edit: this.state.checklist_edit === i ? -1 : i, - }); - this.setState({}); - }} - /> - )} -
- {!this.props.readOnly && ( - { - checklist.splice(i, 1); - this.onChange(checklist); - }} - /> - )} -
- ); - } - }) - } - {!this.props.readOnly && ( - - )} -
- ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/tasks-content.jsx b/twake/frontend/src/app/views/applications/tasks/tasks-content.jsx deleted file mode 100755 index 307534aa..00000000 --- a/twake/frontend/src/app/views/applications/tasks/tasks-content.jsx +++ /dev/null @@ -1,368 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable react/jsx-key */ -import React, { Component } from 'react'; - -import UnconfiguredTab from './unconfigured-tab.jsx'; -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import Emojione from 'components/emojione/emojione'; -import Loader from 'components/loader/loader.jsx'; -import TasksService from 'app/deprecated/Apps/Tasks/Tasks.js'; -import WorkspacesUsers from 'app/features/workspace-members/services/workspace-members-service'; -import Workspaces from 'app/deprecated/workspaces/workspaces.jsx'; -import UserService from 'app/features/users/services/current-user-service'; -import Rounded from 'components/inputs/rounded.jsx'; -import Menu from 'components/menus/menu.jsx'; -import BoardEditor from './board/BoardEditor.jsx'; -import MoreIcon from '@material-ui/icons/MoreHorizOutlined'; -import AlertManager from 'app/features/global/services/alert-manager-service'; -import { getCompanyApplications } from 'app/features/applications/state/company-applications'; -import ConnectorsListManager from 'components/connectors-list-manager/connectors-list-manager.jsx'; -import popupManager from 'app/deprecated/popupManager/popupManager.js'; -import WorkspaceParameter from 'app/views/client/popup/WorkspaceParameter/WorkspaceParameter.jsx'; -import Globals from 'app/features/global/services/globals-twake-app-service'; -import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service'; -import { getCompanyApplication as getApplication } from 'app/features/applications/state/company-applications'; -import Groups from 'app/deprecated/workspaces/groups.js'; - -import Board from './board/Board.jsx'; - -import './tasks.scss'; -import UserListManager from 'app/components/user-list-manager/user-list-manager'; -import RouterService from 'app/features/router/services/router-service'; - -export default class Tasks extends Component { - constructor(props) { - super(props); - this.props = props; - - this.state = {}; - - Languages.addListener(this); - TasksService.addListener(this); - WorkspacesUsers.addListener(this); - const { workspaceId, channelId } = RouterService.getStateFromRoute(); - if (workspaceId && channelId) { - this.boards_collection_key = 'boards_' + workspaceId; - - Collections.get('boards').addListener(this); - Collections.get('boards').addSource( - { - http_base_url: 'tasks/board', - http_options: { - channel_id: channelId, - workspace_id: workspaceId, - }, - websockets: [{ uri: 'boards/' + workspaceId, options: { type: 'board' } }], - }, - this.boards_collection_key, - ); - } - } - - componentWillUnmount() { - Languages.removeListener(this); - TasksService.removeListener(this); - WorkspacesUsers.removeListener(this); - - Collections.get('boards').removeSource(this.boards_collection_key); - } - - getSortingValue(user_id) { - var last_activity = - (WorkspacesUsers.getUsersByWorkspace(Workspaces.currentWorkspaceId)[user_id] || {}) - .last_access || 0; - var val = (new Date().getTime() / 1000 - last_activity) / (60 * 60 * 12); - return 0 - val; - } - - render() { - var current_board = null; - if (((this.props.tab || {}).configuration || {}).board_id) { - current_board = Collections.get('boards').find(this.props.tab.configuration.board_id); - } else if (TasksService.current_board_by_workspace[Workspaces.currentWorkspaceId]) { - if ( - TasksService.current_board_by_workspace[Workspaces.currentWorkspaceId].split('_')[0] === - 'user' - ) { - var user_id = - TasksService.current_board_by_workspace[Workspaces.currentWorkspaceId].split('_')[1]; - var user = Collections.get('users').find(user_id) || {}; - current_board = { - id: TasksService.current_board_by_workspace[Workspaces.currentWorkspaceId], - user_image: UserService.getThumbnail(user), - title: UserService.getFullName(user), - }; - } else { - current_board = Collections.get('boards').find( - TasksService.current_board_by_workspace[Workspaces.currentWorkspaceId], - ); - } - } - - var boards = Collections.get('boards').findBy({ workspace_id: Workspaces.currentWorkspaceId }); - var loading = - !Collections.get('boards').did_load_first_time[this.boards_collection_key] && - boards.length === 0; - - if ( - this.props.tab != null && - (!this.props.tab.configuration || this.props.tab.configuration.board_id === undefined) && - !loading && - !current_board - ) { - return ( - - ); - } - - if (current_board && current_board.id) { - Globals.window.location.hash = '#' + current_board.id; - } else { - Globals.window.location.hash = '#'; - } - - return ( - <> - {loading && ( -
- -
- )} - - {!loading && !current_board && ( -
-
-
- {Languages.t('scenes.apps.tasks.boards', [], 'Boards')} -
- -
- {Languages.t('components.workspace.list_manager.all', [], 'All')} -
- - {boards.map(board => { - return ( -
{ - TasksService.openBoard(board.id); - }} - > -
- {board.emoji && } - {board.title} - - {WorkspaceUserRights.hasWorkspacePrivilege() && ( - { - return ( - - ); - }, - }, - ], - }, - { - text: Languages.t('general.delete', [], 'Delete'), - className: 'error', - onClick: () => { - AlertManager.confirm(() => { - Collections.get('boards').remove( - board, - this.boards_collection_key, - ); - }); - }, - }, - { type: 'separator' }, - { - type: 'menu', - text: Languages.t( - 'scenes.apps.tasks.connectors_menu', - 'Connecteurs...', - ), - submenu: [ - { - type: 'react-element', - reactElement: () => { - var apps = getCompanyApplications(Groups.currentGroupId).filter( - () => false, - ); - if (apps.length > 0) { - return ( - getApplication(id)) - .filter(item => item)} - configurable={item => - ((item.display || {}).configuration || {}) - .can_configure_in_tasks - } - onChange={ids => { - board.connectors = ids; - Collections.get('boards').save( - board, - this.boards_collection_key, - ); - }} - onConfig={app => { - this.configureCalendarConnector(app, board); - }} - /> - ); - } - return ( -
- {Languages.t( - 'scenes.apps.tasks.no_connectors_menu_text', - "Vous n'avez aucun connecteur capable de se connecter à un calendrier.", - )} -
- ); - }, - }, - { type: 'separator' }, - { - type: 'menu', - text: Languages.t( - 'scenes.apps.tasks.connectors_search_menu', - 'Chercher des connecteurs...', - ), - onClick: () => { - popupManager.open( - , - true, - 'workspace_parameters', - ); - }, - }, - ], - }, - ]} - className="options" - > - -
- )} -
-
- {board.active_tasks || '0'}{' '} - {Languages.t('scenes.apps.tasks.active_tasks', 'tâches actives')} -
-
- ); - })} - - {WorkspaceUserRights.hasWorkspacePrivilege() && ( - { - return ( - - ); - }, - }, - ]} - > - - - )} -
- {Languages.t('scenes.apps.tasks.my_tasks', 'Mes tâches')} - -
- TasksService.openBoard('user_' + ids[0])} - /> -
-
- -
-
- )} - - {current_board && ( -
- -
- )} - - ); - } -} diff --git a/twake/frontend/src/app/views/applications/tasks/tasks.scss b/twake/frontend/src/app/views/applications/tasks/tasks.scss deleted file mode 100755 index 8b60dc95..00000000 --- a/twake/frontend/src/app/views/applications/tasks/tasks.scss +++ /dev/null @@ -1,184 +0,0 @@ -.boardPicker { - max-height: 250px; - overflow: auto; - width: 100%; - - .board_frame { - vertical-align: top; - width: 216px; - height: 60px; - border: 1px solid var(--grey-background); - border-radius: var(--border-radius-base); - margin-bottom: 16px; - margin-right: 16px; - display: inline-block; - padding: 16px; - box-sizing: border-box; - position: relative; - cursor: pointer; - - &:hover { - border-color: #fff; - box-shadow: 4px 4px 16px 0 #00000022; - } - - .board_name { - line-height: 24px; - height: 24px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - img.emojione { - margin-right: 8px; - margin-top: -4px; - } - - .board_info { - display: none; - } - } -} - -.tasks_app { - height: 100%; - width: 100%; - left: 0; - right: 0; - flex-direction: column; - padding: 16px 24px; - box-sizing: border-box; - overflow-y: auto; - display: flex; - - .app_title { - .emoji-container { - margin-right: 6px; - margin-top: -4px; - } - .user_head { - display: inline-block; - vertical-align: middle; - margin-right: 8px; - margin-top: -4px; - } - } - - .board_add { - width: 22px; - height: 22px; - line-height: 20px; - padding-top: 2px; - vertical-align: middle; - opacity: 0.8; - margin-top: 35px; - &:hover { - opacity: 1; - } - .m-icon-small { - font-size: 18px !important; - } - } - - .board_frame { - vertical-align: top; - width: 216px; - height: 90px; - border: 1px solid var(--grey-background); - border-radius: var(--border-radius-base); - margin-bottom: 16px; - margin-right: 16px; - display: inline-block; - padding: 16px; - box-sizing: border-box; - position: relative; - cursor: pointer; - - &:hover { - border-color: #fff; - box-shadow: 4px 4px 16px 0 #00000022; - } - - .board_name { - line-height: 24px; - height: 24px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - img.emojione { - margin-right: 6px; - margin-top: -4px; - } - - .board_info { - font-size: 14px; - color: var(--grey-dark); - } - - .options { - opacity: 0; - position: absolute; - right: 16px; - top: 16px; - .m-icon-small { - font-size: 20px !important; - } - } - &:hover { - .options { - opacity: 1; - } - } - } - - .team_member { - display: inline-flex; - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - width: 176px; - height: 42px; - margin-right: 16px; - margin-bottom: 16px; - border-radius: var(--border-radius-base); - cursor: pointer; - - .user_head { - display: inline-block; - } - - .team_username { - flex: 1; - text-align: left; - margin-left: 8px; - min-width: 0px; - .name { - margin-top: 2px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-weight: bold; - font-size: 16px; - margin-bottom: 4px; - } - .username { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 12px; - color: var(--grey-dark); - } - } - } -} - -.unconfigured_tab { - width: 100%; - max-width: 400px; - text-align: center; - margin: auto; - margin-top: 10vh; -} diff --git a/twake/frontend/src/app/views/applications/tasks/tasks.tsx b/twake/frontend/src/app/views/applications/tasks/tasks.tsx deleted file mode 100644 index 3099603e..00000000 --- a/twake/frontend/src/app/views/applications/tasks/tasks.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { ChannelType } from 'app/features/channels/types/channel'; -import { TabType } from 'app/features/tabs/types/tab'; -import React from 'react'; -import { ViewConfiguration } from '../../../features/router/services/app-view-service'; -import { useTab } from '../../../features/tabs/hooks/use-tabs'; -import TasksContent from './tasks-content'; - -type Props = { - channel: ChannelType; - options: ViewConfiguration; -}; - -export default (props: Props) => { - const tabId = props.options?.context?.tabId; - const { tab, save } = useTab(tabId); - - return ( - { - save({ ...tab, configuration }); - }} - /> - ); -}; diff --git a/twake/frontend/src/app/views/applications/tasks/unconfigured-tab.jsx b/twake/frontend/src/app/views/applications/tasks/unconfigured-tab.jsx deleted file mode 100755 index c8498310..00000000 --- a/twake/frontend/src/app/views/applications/tasks/unconfigured-tab.jsx +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from 'react'; - -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import Languages from 'app/features/global/services/languages-service'; -import BoardPicker from './board-picker/board-picker.jsx'; -import Menu from 'components/menus/menu.jsx'; -import Button from 'components/buttons/button.jsx'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import WorkspaceService from 'app/deprecated/workspaces/workspaces'; -import RouterService from 'app/features/router/services/router-service'; - -export default class UnconfiguredTab extends Component { - constructor() { - super(); - - this.state = { - i18n: Languages, - }; - - Languages.addListener(this); - } - componentWillUnmount() { - Languages.removeListener(this); - } - initInBoard(board) { - if (!board.id) { - Menu.closeAll(); - return; - } - if (this.props.saveTab) this.props.saveTab({ board_id: board.id }); - Menu.closeAll(); - } - createFromChannel() { - if (this.creating) { - return; - } - this.creating = true; - var board = Collections.get('boards').editCopy({}); - board.workspace_id = RouterService.getStateFromRoute().workspaceId; - board.title = this.props.channel.name; - board.emoji = this.props.channel.icon; - Collections.get('boards').save(board, this.props.collectionKey, board => { - this.creating = false; - this.initInBoard(board); - }); - } - render() { - return ( -
-
-
{this.props.tab.name}
-
- {Languages.t( - 'scenes.apps.tasks.unconfigured_tab', - [], - "Cet onglet n'est pas encore configuré.", - )} -
- - {AccessRightsService.getCompanyLevel(WorkspaceService.currentGroupId) !== 'guest' && ( - <> -
- - - - this.initInBoard(board)} />, - }, - ]} - style={{ display: 'inline-block' }} - > - - - - )} -
-
- ); - } -} diff --git a/twake/frontend/src/app/views/client/app.tsx b/twake/frontend/src/app/views/client/app.tsx index 366a2c7c..7744161e 100755 --- a/twake/frontend/src/app/views/client/app.tsx +++ b/twake/frontend/src/app/views/client/app.tsx @@ -1,7 +1,7 @@ -import React, { Suspense } from 'react'; +import { Suspense } from 'react'; -import Client from 'app/views/client'; import InitService from 'app/features/global/services/init-service'; +import Client from 'app/views/client'; export default () => { const isAppReady = InitService.useWatcher(() => InitService.app_ready); diff --git a/twake/frontend/src/app/views/applications/drive/browser.tsx b/twake/frontend/src/app/views/client/body/drive/browser.tsx similarity index 73% rename from twake/frontend/src/app/views/applications/drive/browser.tsx rename to twake/frontend/src/app/views/client/body/drive/browser.tsx index 75f03fdc..cd0fa6ab 100644 --- a/twake/frontend/src/app/views/applications/drive/browser.tsx +++ b/twake/frontend/src/app/views/client/body/drive/browser.tsx @@ -14,7 +14,7 @@ import useRouterCompany from 'app/features/router/hooks/use-router-company'; import _ from 'lodash'; import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil'; -import { DrivePreview } from '../viewer/drive-preview'; +import { DrivePreview } from '../../viewer/drive-preview'; import { useOnBuildContextMenu } from './context-menu'; import { DocumentRow } from './documents/document-row'; import { FolderRow } from './documents/folder-row'; @@ -59,8 +59,7 @@ export default memo( loading: loadingParent, path, } = useDriveItem(parentId); - const { item: trash } = useDriveItem('trash'); - const { uploadTree, uploadFromUrl } = useDriveUpload(); + const { uploadTree } = useDriveUpload(); const loading = loadingParent || loadingParentChange; @@ -118,7 +117,8 @@ export default memo( return ( - uploadZoneRef.current?.open()} - addFromUrl={(url, name) => { - setCreationModalState({ parent_id: '', open: false }); - uploadFromUrl(url, name, { - companyId, - parentId, - }); - }} - /> @@ -156,33 +146,20 @@ export default memo(
- {document.location.origin.includes('canary') && access !== 'read' && !inPublicSharing && ( -
- - Welcome to the next version of Twake Drive. -
- Your documents are not migrated yet, you can switch back to{' '} - - https://web.twake.app - {' '} - to see all your documents. Documents added here will not be visible yet on - production but will be kept after the final migration. - -
- )} -
+
+ {access !== 'read' && ( {formatBytes(item?.size || 0)} used in this folder )} onBuildContextMenu(details)}> {' '} -
- {!(inTrash || access === 'read') && ( -
openItemModal()} - > - -
- )} - - {item?.id === 'trash' && ( -
- You are in the trash -
- -
-
- )} - {access !== 'read' && item?.id === 'root' && ( -
- Welcome to your company drive. -
- - {formatBytes(item?.size || 0)} - <Base> used, </Base> <Base>{formatBytes(trash?.size || 0)} in trash</Base> - -
-
- )} - {item?.id !== 'root' && item?.id !== 'trash' &&
} -
{folders.length > 0 && ( <> diff --git a/twake/frontend/src/app/views/applications/drive/components/public-icon.tsx b/twake/frontend/src/app/views/client/body/drive/components/public-icon.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/components/public-icon.tsx rename to twake/frontend/src/app/views/client/body/drive/components/public-icon.tsx diff --git a/twake/frontend/src/app/views/applications/drive/components/select-users.tsx b/twake/frontend/src/app/views/client/body/drive/components/select-users.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/components/select-users.tsx rename to twake/frontend/src/app/views/client/body/drive/components/select-users.tsx diff --git a/twake/frontend/src/app/views/applications/drive/context-menu.tsx b/twake/frontend/src/app/views/client/body/drive/context-menu.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/context-menu.tsx rename to twake/frontend/src/app/views/client/body/drive/context-menu.tsx diff --git a/twake/frontend/src/app/views/applications/drive/documents/common.tsx b/twake/frontend/src/app/views/client/body/drive/documents/common.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/documents/common.tsx rename to twake/frontend/src/app/views/client/body/drive/documents/common.tsx diff --git a/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx b/twake/frontend/src/app/views/client/body/drive/documents/document-row.tsx similarity index 98% rename from twake/frontend/src/app/views/applications/drive/documents/document-row.tsx rename to twake/frontend/src/app/views/client/body/drive/documents/document-row.tsx index e721330d..6db57a7f 100644 --- a/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx +++ b/twake/frontend/src/app/views/client/body/drive/documents/document-row.tsx @@ -15,7 +15,7 @@ import { useDrivePreview } from 'app/features/drive/hooks/use-drive-preview'; import { formatBytes } from 'app/features/drive/utils'; import fileUploadApiClient from 'app/features/files/api/file-upload-api-client'; import { useState } from 'react'; -import Avatar from '../../../../atoms/avatar'; +import Avatar from '../../../../../atoms/avatar'; import { PublicIcon } from '../components/public-icon'; import { CheckableIcon, DriveItemProps } from './common'; diff --git a/twake/frontend/src/app/views/applications/drive/documents/folder-row.tsx b/twake/frontend/src/app/views/client/body/drive/documents/folder-row.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/documents/folder-row.tsx rename to twake/frontend/src/app/views/client/body/drive/documents/folder-row.tsx diff --git a/twake/frontend/src/app/views/applications/drive/drive-item.tsx b/twake/frontend/src/app/views/client/body/drive/drive-item.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/drive-item.tsx rename to twake/frontend/src/app/views/client/body/drive/drive-item.tsx diff --git a/twake/frontend/src/app/views/applications/drive/header-path.tsx b/twake/frontend/src/app/views/client/body/drive/header-path.tsx similarity index 98% rename from twake/frontend/src/app/views/applications/drive/header-path.tsx rename to twake/frontend/src/app/views/client/body/drive/header-path.tsx index 4f8838df..90e50aea 100644 --- a/twake/frontend/src/app/views/applications/drive/header-path.tsx +++ b/twake/frontend/src/app/views/client/body/drive/header-path.tsx @@ -31,7 +31,7 @@ export const PathRender = ({ onClick: (id: string) => void; }) => { return ( - + <Title className="overflow-hidden whitespace-nowrap mr-2 pl-px inline-flex"> {(path || [])?.map((a, i) => ( <PathItem key={a.id} diff --git a/twake/frontend/src/app/views/client/body/drive/index.tsx b/twake/frontend/src/app/views/client/body/drive/index.tsx new file mode 100644 index 00000000..d6718045 --- /dev/null +++ b/twake/frontend/src/app/views/client/body/drive/index.tsx @@ -0,0 +1,26 @@ +import Browser from './browser'; +import { SelectorModal } from './modals/selector'; + +export type EmbedContext = { + companyId?: string; + workspaceId?: string; + channelId?: string; + tabId?: string; +}; + +export default ({ + initialParentId, + context, + inPublicSharing, +}: { + initialParentId?: string; + context?: EmbedContext; + inPublicSharing?: boolean; +}) => { + return ( + <> + <SelectorModal /> + <Browser initialParentId={initialParentId} inPublicSharing={inPublicSharing} /> + </> + ); +}; diff --git a/twake/frontend/src/app/views/applications/drive/modals/confirm-delete/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/confirm-delete/index.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/confirm-delete/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/confirm-delete/index.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/confirm-trash/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/confirm-trash/index.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/confirm-trash/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/confirm-trash/index.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/create/create-folder.tsx b/twake/frontend/src/app/views/client/body/drive/modals/create/create-folder.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/create/create-folder.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/create/create-folder.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx similarity index 98% rename from twake/frontend/src/app/views/applications/drive/modals/create/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx index a68a9d03..c28d57b9 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx +++ b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx @@ -146,7 +146,7 @@ const CreateModalOption = (props: { icon: ReactNode; text: string; onClick: () = return ( <div onClick={props.onClick} - className="flex flex-row p-4 bg-slate-100 hover:bg-slate-200 cursor-pointer rounded-md m-2" + className="flex flex-row p-4 bg-zinc-100 hover:bg-zinc-200 cursor-pointer rounded-md m-2" > <div className="flex items-center justify-center">{props.icon}</div> <div className="grow flex items-center ml-2"> diff --git a/twake/frontend/src/app/views/applications/drive/modals/properties/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/properties/index.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/properties/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/properties/index.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/selector/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/selector/index.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/selector/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/selector/index.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/common.tsx b/twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/update-access/common.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/update-access/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx b/twake/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx b/twake/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx diff --git a/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/versions/index.tsx similarity index 97% rename from twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx rename to twake/frontend/src/app/views/client/body/drive/modals/versions/index.tsx index 29f53518..003c1016 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx +++ b/twake/frontend/src/app/views/client/body/drive/modals/versions/index.tsx @@ -64,7 +64,7 @@ const VersionModalContent = ({ id }: { id: string }) => { }} > {access !== 'read' && ( - <div className={'flex flex-row items-center bg-slate-100 rounded-md mb-4 p-4'}> + <div className={'flex flex-row items-center bg-zinc-100 rounded-md mb-4 p-4'}> <div className="flex flex-row"> <div className="grow flex items-center"> <Base> diff --git a/twake/frontend/src/app/views/applications/drive/shared.tsx b/twake/frontend/src/app/views/client/body/drive/shared.tsx similarity index 80% rename from twake/frontend/src/app/views/applications/drive/shared.tsx rename to twake/frontend/src/app/views/client/body/drive/shared.tsx index 22760bd4..4188cb51 100755 --- a/twake/frontend/src/app/views/applications/drive/shared.tsx +++ b/twake/frontend/src/app/views/client/body/drive/shared.tsx @@ -5,12 +5,12 @@ import Api from 'app/features/global/framework/api-service'; import Languages from 'app/features/global/services/languages-service'; import { addApiUrlIfNeeded } from 'app/features/global/utils/URLUtils'; import RouterService from 'app/features/router/services/router-service'; -import Drive from 'app/views/applications/drive'; -import Avatar from '../../../atoms/avatar'; -import { setPublicLinkToken } from '../../../features/drive/api-client/api-client'; +import Drive from 'app/views/client/body/drive'; +import Avatar from '../../../../atoms/avatar'; +import { setPublicLinkToken } from '../../../../features/drive/api-client/api-client'; import { useParams } from 'react-router-dom'; import shortUUID from 'short-uuid'; -import useRouterCompany from '../../../features/router/hooks/use-router-company'; +import useRouterCompany from '../../../../features/router/hooks/use-router-company'; export default () => { const companyId = useRouterCompany(); @@ -55,17 +55,13 @@ export default () => { <div className="shrink-0"> <a href="https://twake.app" target="_BLANK" rel="noreferrer" className="!text-white"> <span className="nomobile text-white"> - {Languages.t( - 'scenes.app.mainview.create_account', - [], - 'Créez votre espace de travail gratuitement sur ', - )} + {Languages.t('scenes.app.mainview.create_account')} </span> - Twake   👉 + Twake Workplace   👉 </a> </div> </div> - <div className="main-view public "> + <div className="main-view public p-4"> <Drive initialParentId={documentId} inPublicSharing /> </div> <MenusBodyLayer /> diff --git a/twake/frontend/src/app/views/client/main-view/MainView.tsx b/twake/frontend/src/app/views/client/body/index.tsx similarity index 70% rename from twake/frontend/src/app/views/client/main-view/MainView.tsx rename to twake/frontend/src/app/views/client/body/index.tsx index 6bc92526..f4ffcad0 100644 --- a/twake/frontend/src/app/views/client/main-view/MainView.tsx +++ b/twake/frontend/src/app/views/client/body/index.tsx @@ -7,12 +7,7 @@ import CompanyBillingBanner from 'app/components/on-boarding/company-billing-ban import useRouterChannel from 'app/features/router/hooks/use-router-channel'; import useRouterCompany from 'app/features/router/hooks/use-router-company'; import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import MainContent from './MainContent'; -import Search from './MainHeader/Search'; -import './MainView.scss'; -import CompanyHeader from '../channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader'; -import CurrentUser from '../channels-bar/Parts/CurrentUser/CurrentUser'; -import Footer from '../channels-bar/Parts/Footer'; +import Drive from './drive'; type PropsType = { className?: string; @@ -24,22 +19,19 @@ const MainView: FC<PropsType> = ({ className }) => { const channelId = useRouterChannel(); return ( - <Layout className={'global-view-layout ' + (className ? className : '')}> + <div className={'grow min-h-full relative ' + (className ? className : '')}> <AccountStatusComponent /> {companyId && <CompanyBillingBanner companyId={companyId || ''} />} - <div className="bg-white flex space-between w-full"> - <CurrentUser /> - <Search /> - <Footer /> - </div> <MainContentWrapper key={companyId + workspaceId + channelId} /> - </Layout> + </div> ); }; //This is used to delay render and make rest of UI faster export const MainContentWrapper = () => { const [delayed, setDelayed] = useState(true); + const companyId = useRouterCompany(); + const workspaceId = useRouterWorkspace(); //This delay make the app superfast useEffect(() => { @@ -50,7 +42,14 @@ export const MainContentWrapper = () => { return <></>; } - return <MainContent />; + return ( + <Drive + context={{ + companyId, + workspaceId, + }} + /> + ); }; export default ({ className }: PropsType) => { diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.scss b/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.scss deleted file mode 100755 index 07dccd3f..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.scss +++ /dev/null @@ -1,193 +0,0 @@ -.current-company-header { - display: flex; - margin-left: 12px; - margin-right: 12px; - width: auto; - box-sizing: border-box; - - .notifications { - cursor: pointer; - color: var(--black-alpha-50); - font-size: 16px; - - .bell { - display: flex; - align-items: center; - cursor: pointer; - height: 24px; - - &:hover { - background: var(--primary-background); - border-radius: var(--border-radius-base); - } - - &.disabled { - color: var(--red); - } - &.sleep { - color: var(--warning); - } - } - - .remaning-time { - color: grey; - font-style: italic; - } - } - - .current-company { - flex: 1; - overflow: hidden; - height: 48px; - cursor: pointer; - - .name { - display: flex; - cursor: pointer; - - .text { - height: 22px; - font-size: 16px; - color: var(--secondary); - letter-spacing: 0; - line-height: 22px; - font-weight: 700; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .icon { - height: 22px; - line-height: 22px; - font-size: 20px; - min-width: 24px; - margin-left: -4px; - color: var(--secondary); - } - } - - .user-info { - display: flex; - color: var(--black-alpha-70); - align-items: center; - - .ant-badge-status { - align-items: center; - margin-right: 5px; - - .ant-badge-status-text { - display: none; - } - } - .ant-avatar-image { - width: 16px; - height: 16px; - line-height: 16px; - font-size: 18px; - overflow: clip; - border-radius: 50%; - margin-right: 3px; - vertical-align: top; - } - - .status { - position: relative; - display: inline-block; - margin-right: 4px; - width: 8px; - height: 8px; - border-radius: 6px; - background: var(--green); - border: 1px solid var(--grey-background); - - &.grey { - background: var(--grey-dark); - } - &.red { - background: var(--red); - } - } - - .icon { - display: flex; - align-items: center; - padding-right: 2px; - margin-right: 4px; - cursor: pointer; - } - - .text { - font-size: 14px; - line-height: 20px; - flex: 1; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - .emoji-container { - margin-right: 3px; - display: inline-block; - height: 16px; - } - } - } -} - -.loading_render { - .current_user { - pointer-events: none; - .user_head { - background: #dfdfdf !important; - } - .user_head, - .name, - .subname { - background: var(--primary-background); - animation-duration: 1s; - animation-fill-mode: forwards; - animation-iteration-count: infinite; - animation-name: placeHolderShimmer; - animation-timing-function: linear; - background: var(--primary-background); - background: linear-gradient( - to right, - var(--primary-background) 8%, - #f2f2f2 18%, - var(--primary-background) 33% - ); - background-size: 800px 104px; - & > * { - display: none !important; - } - } - .name { - background: #dfdfdf !important; - height: 16px; - border-radius: var(--border-radius-large); - width: 70%; - margin-top: 2px; - } - .subname { - margin-top: 6px; - height: 16px; - border-radius: var(--border-radius-large); - width: 90%; - } - } -} - -.current_company_header_loader { - display: flex; - width: auto; - box-sizing: border-box; - - .current_company_loader { - flex: 1; - overflow: hidden; - height: 48px; - .ant-skeleton > .ant-skeleton-content { - height: 48px; - } - } -} diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.tsx b/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.tsx deleted file mode 100755 index 5215a8b9..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { Badge } from 'antd'; - -import UserService from 'app/features/users/services/current-user-service'; -import Icon from 'app/components/icon/icon.jsx'; -import Emojione from 'components/emojione/emojione'; - -import './CompanyHeader.scss'; -import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; -import { useCurrentWorkspace } from 'app/features/workspaces/hooks/use-workspaces'; - -type PropsType = { - onClickUser?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; - refDivUser: React.LegacyRef<HTMLDivElement> | undefined; -}; - -export default (props: PropsType): JSX.Element => { - const { user } = useCurrentUser(); - const { workspace } = useCurrentWorkspace(); - - return ( - <div className="current-company-header"> - <div - ref={props.refDivUser} - className="current-company" - onClick={evt => props.onClickUser && props.onClickUser(evt)} - > - <div className="name"> - <div className="text">{workspace?.name || '-'}</div> - <div className="icon"> - <Icon type="angle-down" /> - </div> - </div> - - {user && ( - <div className="user-info"> - <Badge count={1} size="small" dot color="green" /> - {!!user.status && ( - <Emojione className="emoji-status" type={user.status.split(' ')[0]} /> - )} - - <span className="text"> - {UserService.getFullName(user)} ({user.email}) - </span> - </div> - )} - </div> - </div> - ); -}; diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CurrentUser.jsx b/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CurrentUser.jsx deleted file mode 100755 index 3d7fde39..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/CurrentUser.jsx +++ /dev/null @@ -1,268 +0,0 @@ -// eslint-disable-next-line no-use-before-define -import React, { Component } from 'react'; - -import Languages from 'app/features/global/services/languages-service'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import UserService from 'app/features/users/services/current-user-service'; -import MenusManager from 'app/components/menus/menus-manager.jsx'; -import LoginService from 'app/features/auth/login-service'; -import WorkspaceService from 'app/deprecated/workspaces/workspaces.jsx'; -import WorkspacesUsers from 'app/features/workspace-members/services/workspace-members-service'; -import ListenUsers from 'app/features/users/services/listen-users-service'; -import WorkspaceParameter from 'app/views/client/popup/WorkspaceParameter/WorkspaceParameter.jsx'; -import AccountParameter from 'app/views/client/popup/UserParameter/UserParameter'; -import WorkspaceUserRights from 'app/features/workspaces/services/workspace-user-rights-service'; -import NotificationParameters from 'app/deprecated/user/notification_parameters.js'; -import CreateWorkspacePage from 'app/views/client/popup/CreateWorkspacePage/CreateWorkspacePage.jsx'; -import CompanyHeaderUI from 'app/views/client/channels-bar/Parts/CurrentUser/CompanyHeader/CompanyHeader'; -import ModalManagerDepreciated from 'app/deprecated/popupManager/popupManager'; -import InitService from 'app/features/global/services/init-service'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import Workspaces from 'app/deprecated/workspaces/workspaces.jsx'; -import FeatureTogglesService, { - FeatureNames, -} from 'app/features/global/services/feature-toggles-service'; -import LockedWorkspacePopup from 'app/components/locked-features-components/locked-workspace-popup/locked-workspace-popup'; -import ModalManager from 'app/components/modal/modal-manager'; -import CompanyMessagesCounter from 'components/company-messages-counter/company-messages-counter'; -import ConsoleService from 'app/features/console/services/console-service'; -import MenuCompanyHeader from './MenuCompanyHeader'; - -export default class CurrentUser extends Component { - constructor() { - super(); - - this.users_repository = Collections.get('users'); - this.i18n = Languages; - - this.state = { - new_status: ['', ''], - showingMenu: false, - }; - - this.preferedEmojisStatus = [ - 'trash', - ':palm_tree:', - ':calendar:', - ':red_car:', - ':hamburger:', - ':sleeping_accommodation:', - ]; - } - - UNSAFE_componentWillMount() { - this.user_id = UserService.getCurrentUserId(); - - ListenUsers.listenUser(this.user_id); - Collections.get('users').addListener(this); - Collections.get('users').listenOnly(this, [ - Collections.get('users').find(this.user_id).front_id, - ]); - Languages.addListener(this); - this.refreshUserState = setInterval(() => { - this.setState({}); - }, 60000); - } - componentWillUnmount() { - ListenUsers.cancelListenUser(this.user_id); - Collections.get('users').removeListener(this); - Languages.removeListener(this); - clearInterval(this.refreshUserState); - } - - componentDidMount() { - const new_status = { - ...this.users_repository.known_objects_by_id[this.user_id].status.split(' '), - }; - if (!new_status[0]) { - new_status[1] = ''; - } - this.setState({ new_status } || [new_status[0], '']); - } - - onClickUser() { - this.showMenu(); - this.setState({ showingMenu: !this.state.showingMenu }); - } - - hideMenu() { - MenusManager.closeMenu(); - } - - showMenu() { - var current_user = this.users_repository.known_objects_by_id[this.user_id]; - var usermenu = [ - { - type: 'react-element', - reactElement: () => <MenuCompanyHeader />, - }, - ]; - - if (!FeatureTogglesService.isActiveFeatureName(FeatureNames.MESSAGE_HISTORY)) { - usermenu.push( - ...[ - { type: 'separator' }, - { - type: 'react-element', - className: 'menu-cancel-left-padding', - reactElement: () => <CompanyMessagesCounter />, - }, - ], - ); - } - - if ( - !WorkspaceUserRights.isGroupInvite() && - (AccessRightsService.hasLevel(Workspaces.currentWorkspaceId, 'member') || - AccessRightsService.hasCompanyLevel(Workspaces.currentGroupId, 'admin')) - ) { - usermenu.push({ type: 'separator' }); - - usermenu.push({ - type: 'menu', - icon: 'users-alt', - text: Languages.t('scenes.app.channelsbar.currentuser.collaborateurs'), - onClick: () => { - ModalManagerDepreciated.open( - <WorkspaceParameter initial_page={2} />, - true, - 'workspace_parameters', - ); - }, - }); - - usermenu.push({ - type: 'menu', - icon: 'plus', - text: Languages.t('scenes.app.channelsbar.currentuser.create_workspace_page'), - onClick: () => { - if (FeatureTogglesService.isActiveFeatureName(FeatureNames.MULTIPLE_WORKSPACES)) { - ModalManagerDepreciated.open(<CreateWorkspacePage />); - } else { - ModalManager.open( - <LockedWorkspacePopup />, - { - position: 'center', - size: { width: '600px' }, - }, - false, - ); - } - }, - }); - } - - if (!WorkspaceUserRights.isInvite()) { - if (WorkspaceUserRights.hasWorkspacePrivilege()) { - usermenu.push( - { type: 'separator' }, - { - type: 'menu', - icon: 'cog', - text: Languages.t('scenes.app.channelsbar.currentuser.workspace_parameters'), - onClick: () => { - ModalManagerDepreciated.open(<WorkspaceParameter />, true, 'workspace_parameters'); - }, - }, - ); - - if ( - AccessRightsService.hasCompanyLevel(WorkspaceService.currentGroupId, 'member') && - InitService.server_infos?.configuration?.accounts?.type === 'console' - ) { - usermenu.push({ - type: 'menu', - icon: 'home', - text: Languages.t('scenes.app.popup.workspaceparameter.pages.company_identity_title'), - rightIcon: 'external-link-alt', - onClick: () => { - return window.open( - ConsoleService.getCompanyManagementUrl(WorkspaceService.currentGroupId), - '_blank', - ); - }, - }); - } - } - } else { - usermenu.push( - { type: 'separator' }, - { - type: 'text', - text: Languages.t('scenes.app.channelsbar.currentuser.invited_status'), - }, - { - type: 'menu', - text: Languages.t('scenes.app.popup.workspaceparameter.pages.quit_workspace_menu'), - icon: 'plane-fly', - className: 'error', - onClick: () => { - WorkspacesUsers.leaveWorkspace(); - }, - }, - ); - } - - usermenu = usermenu.concat([ - { type: 'separator' }, - - { - type: 'menu', - icon: 'user', - text: Languages.t('scenes.app.channelsbar.currentuser.title', [], 'Paramètres du compte'), - onClick: () => { - ModalManagerDepreciated.open(<AccountParameter />, true, 'account_parameters'); - }, - }, - { - type: 'menu', - icon: 'sign-out-alt', - text: Languages.t('scenes.app.channelsbar.currentuser.logout', [], 'Se déconnecter'), - className: 'error', - onClick: () => { - LoginService.logout(); - }, - }, - ]); - - var pos = window.getBoundingClientRect(this.node); - pos.x = pos.x || pos.left; - pos.y = pos.y || pos.top; - - MenusManager.openMenu( - usermenu, - { x: pos.x + pos.width / 2, y: pos.y + pos.height + 10 }, - 'bottom', - ); - } - - render() { - var current_user = this.users_repository.known_objects_by_id[this.user_id]; - - if (!current_user) { - return ''; - } - - var notifications_disabled = false; - if ( - current_user && - NotificationParameters.hasNotificationsDisabled(current_user.notifications_preferences) - ) { - notifications_disabled = true; - } - var status = NotificationParameters.getNotificationsStatus(current_user); - - return ( - <CompanyHeaderUI - refDivUser={node => (this.node = node)} - refDivBell={node => (this.bell_node = node)} - status={status} - notificationsDisabled={notifications_disabled} - onClickUser={evt => { - this.onClickUser(evt); - }} - style={{ marginBottom: 10 }} - /> - ); - } -} diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/MenuCompanyHeader.tsx b/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/MenuCompanyHeader.tsx deleted file mode 100644 index 25eb78bc..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/CurrentUser/MenuCompanyHeader.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import environment from 'app/environment/environment'; -import { CurrentCompanyLogo } from 'app/views/client/workspaces-bar/components/CompanySelector'; -import { useCurrentCompany } from 'app/features/companies/hooks/use-companies'; -import { useCurrentWorkspace } from 'app/features/workspaces/hooks/use-workspaces'; -import React from 'react'; - -export default () => { - const { company } = useCurrentCompany(); - const { workspace } = useCurrentWorkspace(); - - return ( - <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'start' }}> - <div style={{ width: 32, height: 32, marginRight: 8 }}> - <CurrentCompanyLogo size={32} withCompanyName={false} /> - </div> - <div style={{ flex: 1, lineHeight: '1.2em' }}> - <span - style={{ - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - maxWidth: '180px', - display: 'inline-block', - }} - > - <b> - {(company.name || '').substr(0, 16)} - {workspace?.name} - </b> - </span> - <span style={{ opacity: 0.5 }}> - {environment.api_root_url.split('//').pop()?.split('/').shift()} - </span> - <span className="link" style={{ marginLeft: 8 }}> - Plan {company.plan?.name} - </span> - </div> - </div> - ); -}; diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer.jsx b/twake/frontend/src/app/views/client/channels-bar/Parts/Footer.jsx deleted file mode 100755 index f952a30b..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { Component } from 'react'; - -import Languages from 'app/features/global/services/languages-service'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import Collections from 'app/deprecated/CollectionsV1/Collections/Collections.js'; -import InitService from 'app/features/global/services/init-service'; - -import FooterUI from 'app/views/client/channels-bar/Parts/Footer/Footer.jsx'; - -export default class Footer extends Component { - constructor() { - super(); - - this.state = { - i18n: Languages, - }; - - Languages.addListener(this); - Collections.get('groups').addListener(this); - Collections.get('groups').listenOnly(this, [Groups.currentGroupId]); - } - componentWillUnmount() { - Languages.removeListener(this); - Collections.get('groups').removeListener(this); - } - shouldComponentUpdate() { - Collections.get('groups').listenOnly(this, [Groups.currentGroupId]); - return true; - } - render() { - var group = Collections.get('groups').find(Groups.currentGroupId); - - return ( - <FooterUI - planName={group?.plan} - onClickHelp={ - InitService.server_infos?.configuration?.help_url && - (() => { - window.open(InitService.server_infos?.configuration?.help_url); - }) - } - onClickDocumentation={() => { - window.open('https://linagora.github.io/Twake/index.html'); - }} - /> - ); - } -} diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.jsx b/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.jsx deleted file mode 100755 index ffb22bf5..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.jsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; - -import './Footer.scss'; - -import Icon from 'app/components/icon/icon.jsx'; -import InitService from 'app/features/global/services/init-service'; -import Languages from 'app/features/global/services/languages-service'; -import Globals from 'app/features/global/services/globals-twake-app-service'; -import Menu from 'components/menus/menu.jsx'; -import Version from 'app/environment/version'; - -export default class Footer extends React.Component { - constructor(props) { - super(); - this.onpremise = !!((InitService.server_infos || {}).branding || {}).name; - this.menu = [ - { type: 'text', text: `Twake v${Version.version_detail}` }, - ...(props.onClickHelp - ? [ - { - type: 'menu', - text: Languages.t('general.help.support', [], 'Support'), - icon: 'comment', - onClick: props.onClickHelp, - }, - ] - : []), - { - type: 'menu', - text: Languages.t('general.help.documentation', [], 'Documentation'), - icon: 'book', - onClick: props.onClickDocumentation, - }, - ]; - } - componentDidMount() { - try { - if (!this.onpremise) { - var config = { - selector: '#changelog_text', - account: '7L9kKJ', - translations: { - title: 'Twake Changelog', - readMore: 'Read more', - footer: 'View more changes', - }, - }; - if (typeof Globals.window?.Headway != 'undefined') { - Globals.window.Headway.init(config); - } - } - } catch (err) { - console.log(err); - } - } - render() { - return ( - <div className="channel_footer"> - <div className="line_2"> - {this.props.onClickHelp && ( - <Menu menu={this.menu} className="options" position="top"> - <div className="help"> - <Icon type="question-circle" /> - {Languages.t('general.help', [], 'Help')} - </div> - </Menu> - )} - {!this.onpremise && ( - <div - className="help" - style={{ textAlign: 'right', flex: 1, position: 'relative' }} - id="changelog_text" - > - Changelog - </div> - )} - {this.onpremise && ( - <div className={'plan'}> - {((InitService.server_infos || {}).branding || {}).name} Server - </div> - )} - </div> - </div> - ); - } -} diff --git a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.scss b/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.scss deleted file mode 100755 index e2ec0a01..00000000 --- a/twake/frontend/src/app/views/client/channels-bar/Parts/Footer/Footer.scss +++ /dev/null @@ -1,95 +0,0 @@ -#HW_frame_cont { - left: 70px !important; -} - -.channel_footer { - border-top: 1px solid var(--grey-background); - flex-direction: column; - box-sizing: border-box; - display: flex; - - & > div { - display: flex; - flex-direction: row; - padding: 11px 14px; - box-sizing: border-box; - } - - .line_1 { - line-height: 24px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - color: var(--grey-dark); - font-size: 14px; - - .group_switch { - margin-right: 8px; - height: 24px; - .current_company_logo { - width: 24px; - height: 24px; - border-radius: var(--border-radius-base); - } - } - } - - .line_2 { - height: 46px; - } - - #HW_badge_cont { - position: absolute; - top: -6px; - right: 0; - width: 90px; - - span.HW_visible { - background: #827dff; - } - } - - .help { - font-size: 12px; - color: var(--grey-dark); - line-height: 22px; - width: auto; - float: left; - cursor: pointer; - - &:hover { - color: var(--black); - } - } - - .plan { - border-radius: var(--border-radius-base); - font-size: 12px; - height: 22px; - float: right; - line-height: 22px; - vertical-align: middle; - background: #e2f4e8; - color: var(--green); - padding: 0px 4px; - font-weight: 700; - text-transform: uppercase; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: auto; - cursor: pointer; - display: inline; - - &.free { - background: #fff3cd; - color: #ff9d00; - } - } -} - -.loading_render { - .channel_footer { - display: none !important; - } -} diff --git a/twake/frontend/src/app/views/client/common/account.tsx b/twake/frontend/src/app/views/client/common/account.tsx new file mode 100644 index 00000000..c85c0031 --- /dev/null +++ b/twake/frontend/src/app/views/client/common/account.tsx @@ -0,0 +1,58 @@ +import Avatar from 'app/atoms/avatar'; +import { Base, Info } from 'app/atoms/text'; +import Menu from 'app/components/menus/menu'; +import LoginService from 'app/features/auth/login-service'; +import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; +import currentUserService from 'app/features/users/services/current-user-service'; +import AccountParameter from 'app/views/client/popup/UserParameter/UserParameter'; +import Languages from '../../../features/global/services/languages-service'; +import ModalManagerDepreciated from 'app/deprecated/popupManager/popupManager'; + +export default ({ sidebar }: { sidebar?: boolean }): JSX.Element => { + const { user } = useCurrentUser(); + + if (!user) return <></>; + + return ( + <Menu + className="flex flex-row items-center max-w-xs cursor-pointer" + position="bottom" + menu={[ + { + type: 'menu', + icon: 'user', + text: Languages.t('scenes.app.channelsbar.currentuser.title'), + onClick: () => { + ModalManagerDepreciated.open(<AccountParameter />, true, 'account_parameters'); + }, + }, + { + type: 'menu', + icon: 'sign-out-alt', + text: Languages.t('scenes.app.channelsbar.currentuser.logout'), + className: 'error', + onClick: () => { + LoginService.logout(); + }, + }, + ]} + > + <Avatar + size="md" + className="shrink-0 border-0" + avatar={user.thumbnail} + title={currentUserService.getFullName(user)} + /> + <div + className={'sm:block ml-2 mr-2 flex flex-col overflow-hidden ' + (sidebar ? '' : 'hidden')} + > + <Base className="font-bold overflow-hidden text-ellipsis whitespace-nowrap w-full block -mb-1"> + {currentUserService.getFullName(user)} + </Base> + <Info className="font-semibold overflow-hidden text-ellipsis whitespace-nowrap w-full"> + {user.email} + </Info> + </div> + </Menu> + ); +}; diff --git a/twake/frontend/src/app/views/client/common/app-grid.tsx b/twake/frontend/src/app/views/client/common/app-grid.tsx new file mode 100644 index 00000000..390aa3ed --- /dev/null +++ b/twake/frontend/src/app/views/client/common/app-grid.tsx @@ -0,0 +1,49 @@ +import { ViewGridIcon } from '@heroicons/react/outline'; +import { Button } from 'app/atoms/button/button'; +import Menu from 'app/components/menus/menu'; +import { Base } from '../../../atoms/text'; +import InitService from '../../../features/global/services/init-service'; + +export default ({ className }: { className?: string }): JSX.Element => { + const applications = InitService?.server_infos?.configuration?.app_grid || []; + + if (applications.length === 0) { + return <></>; + } + + return ( + <Menu + position="bottom" + menu={[ + { + type: 'react-element', + reactElement: ( + <div className="grid grid-cols-3"> + {applications.map((app, index) => { + return ( + <a + key={index} + target="_blank" + rel="noreferrer" + href={app.url} + className="inline-block flex flex-col items-center justify-center cursor-pointer hover:bg-zinc-100 rounded-md p-2 pb-1" + > + <img src={app.logo} className="w-14 h-14 mb-1" /> + <Base>{app.name}</Base> + </a> + ); + })} + </div> + ), + }, + ]} + > + <Button + theme="default" + size="md" + className={'!rounded-full border-0 ' + className} + icon={() => <ViewGridIcon className="w-6 h-6 text-blue-500" />} + /> + </Menu> + ); +}; diff --git a/twake/frontend/src/app/views/client/common/disk-usage.tsx b/twake/frontend/src/app/views/client/common/disk-usage.tsx new file mode 100644 index 00000000..b44fccb2 --- /dev/null +++ b/twake/frontend/src/app/views/client/common/disk-usage.tsx @@ -0,0 +1,22 @@ +import { Base, Title } from '@atoms/text'; +import { useDriveItem } from '@features/drive/hooks/use-drive-item'; +import { formatBytes } from '@features/drive/utils'; + +export default () => { + const { access, item } = useDriveItem('root'); + const { item: trash } = useDriveItem('trash'); + return ( + <> + {access !== 'read' && item?.id === 'root' && ( + <div className="bg-zinc-500 dark:bg-zinc-800 bg-opacity-10 rounded-md p-4 w-auto max-w-md"> + <div className="w-full"> + <Title> + {formatBytes(item?.size || 0)} + <Base> used, </Base> <Base>{formatBytes(trash?.size || 0)} in trash</Base> + +
+
+ )} + + ); +}; diff --git a/twake/frontend/src/app/views/client/common/search.tsx b/twake/frontend/src/app/views/client/common/search.tsx new file mode 100644 index 00000000..8ac53ffa --- /dev/null +++ b/twake/frontend/src/app/views/client/common/search.tsx @@ -0,0 +1,58 @@ +import { Button } from '@atoms/button/button'; +import { Input } from '@atoms/input/input-text'; +import { AdjustmentsIcon, SearchIcon } from '@heroicons/react/outline'; +import { InputDecorationIcon } from 'app/atoms/input/input-decoration-icon'; +import Languages from 'app/features/global/services/languages-service'; +import RouterServices from 'app/features/router/services/router-service'; +import { useSearchModal } from 'app/features/search/hooks/use-search'; +import { SearchInputState } from 'app/features/search/state/search-input'; +import { useRecoilState } from 'recoil'; + +export default (): JSX.Element => { + const { workspaceId, channelId } = RouterServices.getStateFromRoute(); + const { setOpen: setOpenSearch } = useSearchModal(); + const [searchState, setSearchState] = useRecoilState(SearchInputState); + + const setOpen = () => { + if ( + searchState.query === '' || + (searchState.channelId && searchState.channelId !== channelId) + ) { + setSearchState({ query: searchState.query, workspaceId: workspaceId, channelId: channelId }); + } + setOpenSearch(true); + }; + + return ( + <> +
+ ( + + )} + suffix={() => ( +
+ + ); +}; diff --git a/twake/frontend/src/app/views/client/header/index.tsx b/twake/frontend/src/app/views/client/header/index.tsx new file mode 100644 index 00000000..4b456622 --- /dev/null +++ b/twake/frontend/src/app/views/client/header/index.tsx @@ -0,0 +1,39 @@ +import { MenuIcon } from '@heroicons/react/outline'; +import Account from '../common/account'; +import AppGrid from '../common/app-grid'; +import Search from '../common/search'; + +export default ({ openSideMenu }: { openSideMenu: () => void }) => { + return ( +
+
+ Tdrive + Tdrive +
+
openSideMenu()} + className="sm:hidden block shrink-0 w-10 hover:text-zinc-600 text-zinc-500 cursor-pointer -mx-2 px-2" + > + +
+ +
+ +
+ +
+
+ +
+ +
+ ); +}; diff --git a/twake/frontend/src/app/views/client/index.tsx b/twake/frontend/src/app/views/client/index.tsx index 574f3111..aea5b055 100755 --- a/twake/frontend/src/app/views/client/index.tsx +++ b/twake/frontend/src/app/views/client/index.tsx @@ -1,37 +1,34 @@ // eslint-disable-next-line @typescript-eslint/no-use-before-define -import React, { Suspense, useState } from 'react'; -import { Menu } from 'react-feather'; -import { Layout } from 'antd'; import classNames from 'classnames'; +import React, { Suspense, useState } from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import PopupService from 'app/deprecated/popupManager/popupManager.js'; -import PopupComponent from 'components/popup-component/popup-component.jsx'; -import MainView from './main-view/MainView'; -import DraggableBodyLayer from 'components/draggable/draggable-body-layer.jsx'; -import MenusBodyLayer from 'app/components/menus/menus-body-layer.jsx'; -import DriveUploadViewer from 'components/uploads/upload-viewer.jsx'; -import ChatUploadsViewer from 'app/components/file-uploads/uploads-viewer'; -import ConfigBodyLayer from 'components/configurators/config-body-layer.jsx'; -import Viewer from 'app/views/applications/viewer/viewer'; -import ModalComponent from 'app/components/modal/modal-component'; -import ConnectionIndicator from 'components/connection-indicator/connection-indicator'; -import SearchPopup from 'components/search-popup/search-popup'; -import NewVersionComponent from 'components/new-version/new-version-component'; -import SideBars from './side-bars'; -import CompanyStatusComponent from 'app/components/on-boarding/company-status-component'; -import UserContext from 'app/features/users/state/integration/user-context'; -import { useCurrentUser, useCurrentUserRealtime } from 'app/features/users/hooks/use-current-user'; -import { useFeatureToggles } from 'app/components/locked-features-components/feature-toggles-hooks'; -import useUsetiful from 'app/features/global/hooks/use-usetiful'; import UsersSearchModal from 'app/components/channel-members-list/users-search-modal'; +import ChatUploadsViewer from 'app/components/file-uploads/uploads-viewer'; +import { useFeatureToggles } from 'app/components/locked-features-components/feature-toggles-hooks'; +import MenusBodyLayer from 'app/components/menus/menus-body-layer.jsx'; +import ModalComponent from 'app/components/modal/modal-component'; +import CompanyStatusComponent from 'app/components/on-boarding/company-status-component'; +import PopupService from 'app/deprecated/popupManager/popupManager.js'; +import useUsetiful from 'app/features/global/hooks/use-usetiful'; +import Languages from 'app/features/global/services/languages-service'; +import { useCurrentUser, useCurrentUserRealtime } from 'app/features/users/hooks/use-current-user'; +import UserContext from 'app/features/users/state/integration/user-context'; +import Viewer from 'app/views/client/viewer/viewer'; +import ConnectionIndicator from 'components/connection-indicator/connection-indicator'; +import DraggableBodyLayer from 'components/draggable/draggable-body-layer.jsx'; +import NewVersionComponent from 'components/new-version/new-version-component'; +import PopupComponent from 'components/popup-component/popup-component.jsx'; +import SearchPopup from 'components/search-popup/search-popup'; +import DriveUploadViewer from 'components/uploads/upload-viewer.jsx'; +import MainView from './body'; -import './styles.scss'; -import DownloadAppBanner from 'app/components/download-app-banner/download-app-banner'; import ChannelAttachementList from 'app/components/channel-attachement-list/channel-attachement-list'; +import DownloadAppBanner from 'app/components/download-app-banner/download-app-banner'; import { EditChannelModal } from 'app/components/edit-channel'; import Invitation from 'app/components/invitation/invitation'; -import DesktopRedirect from '../desktop-redirect'; +import DesktopRedirect from '../../components/desktop-redirect'; +import Header from './header'; +import SideBar from './side-bar'; export default React.memo((): JSX.Element => { const [menuIsOpen, setMenuIsOpen] = useState(false); @@ -49,34 +46,52 @@ export default React.memo((): JSX.Element => { if (user?.id) { page = ( - +
+ - - } - breakpoint="lg" - collapsedWidth="0" - theme="light" - width={70} - onCollapse={(collapsed, type) => { - if (type === 'responsive') return setTimeout(() => setMenuIsOpen(false), 0); - setTimeout(() => setMenuIsOpen(!collapsed), 0); - }} - > - }> - - - - }> - - - + <> +
setMenuIsOpen(true)} /> +
+
+ }> + + +
+
setMenuIsOpen(false)} + className="absolute left-0 top-0 h-full w-full bg-black bg-opacity-50 transition-all z-40 opacity-0" + >
+
+ }> + + +
+
+ - +
); } @@ -88,7 +103,6 @@ export default React.memo((): JSX.Element => { - diff --git a/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx b/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx deleted file mode 100644 index 4ab0ecd9..00000000 --- a/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import useRouterCompany from 'app/features/router/hooks/use-router-company'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import AppViewService from 'app/features/router/services/app-view-service'; -import Drive from 'app/views/applications/drive'; -import { FC } from 'react'; - -type PropsType = { - viewService: AppViewService; - id: string; -}; - -const AppView: FC = props => { - const companyId = useRouterCompany(); - const workspaceId = useRouterWorkspace(); - - return ( - - ); -}; -export default AppView; diff --git a/twake/frontend/src/app/views/client/main-view/ContentRestricted.tsx b/twake/frontend/src/app/views/client/main-view/ContentRestricted.tsx deleted file mode 100644 index df8488bf..00000000 --- a/twake/frontend/src/app/views/client/main-view/ContentRestricted.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; -import { LockOpenIcon } from '@heroicons/react/outline'; - -export default () => { - return ( -
-
- -
-
- {Languages.t('scenes.client.join_private_channel.info')} -
-
-
-
- ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/MainContent.tsx b/twake/frontend/src/app/views/client/main-view/MainContent.tsx deleted file mode 100644 index 082d5e8d..00000000 --- a/twake/frontend/src/app/views/client/main-view/MainContent.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { FC, Suspense } from 'react'; - -import { Layout } from 'antd'; -import MainViewService from 'app/features/router/services/main-view-service'; -import SideViewService from 'app/features/router/services/side-view-service'; -import AppView from './AppView/AppView'; - -export const ViewContext = React.createContext({ type: '', id: '' }); - -const MainContent: FC = () => { - const [, mainId] = MainViewService.useWatcher(() => [ - MainViewService.getViewType(), - MainViewService.getId(), - ]); - - // Put the sideview in full screen if screen has not a big width - const { innerWidth } = window; - let sideViewWidth = '40%'; - if (innerWidth < 768) { - sideViewWidth = '100%'; - } - return ( - - - - - - - - }> - - - - - - - - - - ); -}; - -export default MainContent; diff --git a/twake/frontend/src/app/views/client/main-view/MainHeader/Search.tsx b/twake/frontend/src/app/views/client/main-view/MainHeader/Search.tsx deleted file mode 100644 index b2f436e9..00000000 --- a/twake/frontend/src/app/views/client/main-view/MainHeader/Search.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import RouterServices from 'app/features/router/services/router-service'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import Languages from 'app/features/global/services/languages-service'; -import { useSearchModal } from 'app/features/search/hooks/use-search'; -import { Button } from '@atoms/button/button'; -import { SearchIcon } from '@heroicons/react/solid'; -import { Input } from '@atoms/input/input-text'; -import { InputDecorationIcon } from 'app/atoms/input/input-decoration-icon'; -import { useRecoilState } from 'recoil'; -import { SearchInputState } from 'app/features/search/state/search-input'; - -export default (): JSX.Element => { - const { workspaceId, companyId, channelId } = RouterServices.getStateFromRoute(); - const { setOpen: setOpenSearch } = useSearchModal(); - const [searchState, setSearchState] = useRecoilState(SearchInputState); - - const setOpen = () => { - if ( - searchState.query === '' || - (searchState.channelId && searchState.channelId !== channelId) - ) { - setSearchState({ query: searchState.query, workspaceId: workspaceId, channelId: channelId }); - } - setOpenSearch(true); - }; - - const disable = !( - AccessRightsService.hasLevel(workspaceId, 'member') && - AccessRightsService.getCompanyLevel(companyId) !== 'guest' - ); - - return ( - <> -
- {!disable && ( - ( - setOpen()} - /> - )} - /> - )} -
- -
- {!disable && ( -
- - ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/MainView.scss b/twake/frontend/src/app/views/client/main-view/MainView.scss deleted file mode 100644 index 80ebb196..00000000 --- a/twake/frontend/src/app/views/client/main-view/MainView.scss +++ /dev/null @@ -1,168 +0,0 @@ -.ant-layout .main-view-layout { - height: 100%; - overflow: hidden; - .main-view-content { - position: relative; - & > div { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - } - } -} - -.ant-layout .global-view-layout { - &.collapsed .global-view-header { - .channel-name { - margin-left: 26px; - } - .app-name { - margin-left: 40px; - } - } - .global-view-header { - padding: 8px 0; - height: 56px; - background-color: #FFF; - - .ant-divider { - margin: 0; - height: 2px; - } - .ant-row { - white-space: nowrap; - padding: 0 16px; - line-height: 47px; - } - } - - .global-side-view { - background-color: var(--white); - border-left: 1px solid var(--grey-background); - transition-duration: 0s !important; - - .side-header { - height: 40px; - background: #f5f5f7; - background: var(--grey-background); - font-size: 13px; - font-weight: 500; - color: var(--primary); - color: var(--black-alpha-70); - padding: 8px 8px 8px 16px; - line-height: 24px; - box-sizing: border-box; - overflow: hidden; - display: flex; - - span { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - min-width: 0; - flex: 1 1; - } - - svg { - cursor: pointer; - right: 8px; - color: var(--black-alpha-50); - &:hover { - color: var(--black); - } - } - } - } - - .global-view-content { - & > .ant-layout { - max-width: 100%; - } - padding: 0px; - display: flex; - - .main-view-tabs-header { - background-color: #FFF; - height: 47px; - margin-top: -8px; - padding: 0 8px; - } - - .main-view-tabs { - .ant-tabs-nav { - padding: 0 8px; - } - } - } -} - -.main-view, -.main-view-content { - flex: 1; - background: var(--white); - box-sizing: border-box; - display: flex; - flex-direction: column; - position: relative; - border-top: 1px solid var(--grey-background); - - .app_loader { - position: absolute; - margin: auto; - width: 50px; - height: 50px; - right: 0px; - bottom: 0px; - top: 0px; - left: 0px; - } - - .no-channel-text { - color: #000; - position: absolute; - margin: auto; - left: 0; - right: 0; - top: 0; - bottom: 0; - height: 200px; - width: 300px; - text-align: center; - font-size: 16px; - opacity: 0.5; - } -} - -.public_header { - background-color: var(--primary); - flex-direction: column; - width: 100vw; - height: 64px; - display: flex; - flex-direction: row; - - .left { - padding: 16px; - padding-left: 24px; - display: flex; - flex-direction: row; - } - - .right { - flex: 1; - text-align: right; - font-size: 12px; - padding: 16px; - padding-right: 24px; - line-height: 32px; - - a { - color: #fff; - &:hover { - opacity: 0.8; - } - } - } -} diff --git a/twake/frontend/src/app/views/client/main-view/NoApp.tsx b/twake/frontend/src/app/views/client/main-view/NoApp.tsx deleted file mode 100644 index 82acdeeb..00000000 --- a/twake/frontend/src/app/views/client/main-view/NoApp.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import Languages from 'app/features/global/services/languages-service'; - -export default () => { - return ( -
-
- {Languages.t( - 'scenes.app.mainview.instruction_current_tab', - [], - 'Commencez par sélectionner une chaîne sur votre gauche.', - )} -
-
- ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/Tabs/DefaultChannelTab.tsx b/twake/frontend/src/app/views/client/main-view/Tabs/DefaultChannelTab.tsx deleted file mode 100644 index fda65e9c..00000000 --- a/twake/frontend/src/app/views/client/main-view/Tabs/DefaultChannelTab.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import RouterServices from 'app/features/router/services/router-service'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import Menu from 'components/menus/menu.jsx'; -import { MoreHorizontal, MessageCircle } from 'react-feather'; -import Languages from 'app/features/global/services/languages-service'; -import popupManager from 'app/deprecated/popupManager/popupManager.js'; -import WorkspaceParameter from '../../popup/WorkspaceParameter/WorkspaceParameter'; -import { Application } from 'app/features/applications/types/application'; -import ConnectorsListManager from 'app/components/connectors-list-manager/connectors-list-manager'; -import MainViewService from 'app/features/router/services/main-view-service'; -import { isArray } from 'lodash'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import { getCompanyApplication as getApplication } from 'app/features/applications/state/company-applications'; -import { getCompanyApplications } from 'app/features/applications/state/company-applications'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import { useChannel } from 'app/features/channels/hooks/use-channel'; - -export default ({ selected }: { selected: boolean }): JSX.Element => { - const { companyId, workspaceId, channelId } = RouterServices.getStateFromRoute(); - const { channel, save: saveChannel } = useChannel(channelId || ''); - - const configureChannelConnector = (app: Application): void => { - return WorkspacesApps.notifyApp(app.id, 'configuration', 'channel', { - channel: channel, - }); - }; - - const onChange = (ids: string[]): void => { - saveChannel({ ...channel, connectors: ids }); - }; - - const current = () => { - return ( - isArray(channel.connectors) && - channel.connectors - .map((id: string) => getApplication(id)) - .filter(application => application !== undefined) - ); - }; - - if (selected) { - MainViewService.select(MainViewService.getId(), { - app: { - identity: { - code: 'messages', - name: '', - icon: '', - description: '', - website: '', - categories: [], - compatibility: [], - }, - }, - context: { type: 'channel' }, - hasTabs: MainViewService.getConfiguration().hasTabs, - }); - } - - return ( - { - const route: string = RouterServices.generateRouteFromState({ - tabId: '', - }); - return RouterServices.push(route); - }} - > - - - - {Languages.t('scenes.app.mainview.discussion')} - - - {!!selected && - AccessRightsService.hasLevel(workspaceId, 'member') && - AccessRightsService.getCompanyLevel(companyId) !== 'guest' && ( - { - const apps = getCompanyApplications(Groups.currentGroupId).filter( - (app: Application) => - (app?.display?.twake?.configuration || []).includes('channel'), - ); - - if (apps.length) { - return ( - - ); - } - return ( -
- {Languages.t( - 'scenes.app.mainview.tabs.no_connected_connectors_for_channel', - [], - "Vous n'avez aucun connecteur capable de se connecter à une chaîne.", - )} -
- ); - }, - }, - { type: 'separator' }, - { - type: 'menu', - text: Languages.t( - 'scenes.app.mainview.tabs.searching_connectors', - [], - 'Chercher des connecteurs...', - ), - onClick: () => - popupManager.open( - , - true, - ), - }, - ], - }, - ]} - > - -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/Tabs/Tab.tsx b/twake/frontend/src/app/views/client/main-view/Tabs/Tab.tsx deleted file mode 100644 index e221adda..00000000 --- a/twake/frontend/src/app/views/client/main-view/Tabs/Tab.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { TabType } from 'app/features/tabs/types/tab'; -import RouterServices from 'app/features/router/services/router-service'; -import TabsTemplateEditor from './TabsTemplateEditor'; -import ModalManager from 'app/components/modal/modal-manager'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import Menu from 'components/menus/menu.jsx'; -import { MoreHorizontal } from 'react-feather'; -import Languages from 'app/features/global/services/languages-service'; -import { capitalize } from 'lodash'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import MainViewService from 'app/features/router/services/main-view-service'; -import { getCompanyApplication as getApplication } from 'app/features/applications/state/company-applications'; -import { useTab } from 'app/features/tabs/hooks/use-tabs'; - -type PropsType = { - tabId: string; - currentUserId: string; - selected: boolean; -}; - -export default ({ selected, tabId, currentUserId }: PropsType): JSX.Element => { - const { workspaceId, companyId } = RouterServices.getStateFromRoute(); - const { tab, remove, save } = useTab(tabId || ''); - const isCurrentUserAdmin: boolean = AccessRightsService.useWatcher(() => - AccessRightsService.hasLevel(workspaceId, 'moderator'), - ); - - if (!tab) { - return <>; - } - - if (selected && tab) { - MainViewService.select(MainViewService.getId(), { - context: { - tabId: tab.id, - configuration: tab.configuration || {}, - name: tab.name, - type: 'application', - }, - app: getApplication(tab.application_id || ''), - hasTabs: MainViewService.getConfiguration().hasTabs, - }); - } - - return ( - { - const route: string = RouterServices.generateRouteFromState({ - tabId: tab.id, - }); - return RouterServices.push(route); - }} - > - {WorkspacesApps.getAppIconComponent(tab, { size: 14 })} - {capitalize(tab.name)} - {selected && - AccessRightsService.hasLevel(workspaceId, 'member') && - AccessRightsService.getCompanyLevel(companyId) !== 'guest' && ( - - ModalManager.open( - save(item)} />, - { - position: 'center', - size: { width: '600px', minHeight: '329px' }, - }, - ), - }, - { - type: 'menu', - hide: currentUserId !== tab.owner && !isCurrentUserAdmin, - text:
{Languages.t('general.delete')}
, - onClick: () => remove(), - }, - ]} - > - -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.scss b/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.scss deleted file mode 100644 index 27231807..00000000 --- a/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.scss +++ /dev/null @@ -1,57 +0,0 @@ -.align-items-center { - display: flex; - align-items: center; -} -.tabs-global-container { - width: 100%; - height: 47px; - overflow: auto; - .main-view-tabs { - height: 47px; - margin-inline: 8px; - overflow: auto; - - .tab-component { - cursor: pointer; - line-height: 31px; - margin-right: 8px; - padding-inline: 8px; - &:hover { - opacity: 0.8; - color: var(--primary); - } - } - .tab-component-selected { - cursor: pointer; - line-height: 31px; - margin-right: 8px; - padding-inline: 8px; - border-radius: 8px; - text-shadow: 0 0 0.25px currentColor; - color: var(--primary); - background-color: var(--primary-background); - } - - .tab-name { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - max-width: 108px; - } - .add-tab-button { - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border-radius: var(--border-radius-large); - background-color: var(--grey-background); - - &:hover { - background-color: var(--grey-light); - color: var(--grey-dark); - } - } - } -} diff --git a/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.tsx b/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.tsx deleted file mode 100644 index dfffd743..00000000 --- a/twake/frontend/src/app/views/client/main-view/Tabs/Tabs.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { Plus } from 'react-feather'; -import { TabType } from 'app/features/tabs/types/tab'; - -import TabsTemplateEditor from './TabsTemplateEditor'; -import ModalManager from 'app/components/modal/modal-manager'; -import RouterServices from 'app/features/router/services/router-service'; -import DefaultChannelTab from 'app/views/client/main-view/Tabs/DefaultChannelTab'; -import Tab from 'app/views/client/main-view/Tabs/Tab'; -import UserService from 'app/features/users/services/current-user-service'; -import useTabs from 'app/features/tabs/hooks/use-tabs'; -import AccessRightsService from 'app/features/workspace-members/services/workspace-members-access-rights-service'; -import useRouterCompany from '../../../../features/router/hooks/use-router-company'; -import useRouterWorkspace from 'app/features/router/hooks/use-router-workspace'; -import useRouterTab from 'app/features/router/hooks/use-router-tab'; - -import './Tabs.scss'; - -export default (): JSX.Element => { - const workspaceId = useRouterWorkspace(); - const companyId = useRouterCompany(); - const tabId = useRouterTab(); - const { tabs, save } = useTabs(); - - const currentUser = UserService.getCurrentUser(); - const tabsList = [...tabs]; - - if (tabId && tabs.map(e => e.id).indexOf(tabId || '') < 0) { - const route: string = RouterServices.generateRouteFromState({ - tabId: '', - }); - RouterServices.push(route); - } - - return ( -
- {tabsList.sort((a, b) => (a.order || '').localeCompare(b.order || '')) && ( - - - {tabsList.map((tab: TabType) => { - return ( - tab.id && ( - - ) - ); - })} - {AccessRightsService.hasLevel(workspaceId, 'member') && - AccessRightsService.getCompanyLevel(companyId) !== 'guest' && ( -
{ - return ModalManager.open( - save(item)} - />, - { - position: 'center', - size: { width: '600px', minHeight: '329px' }, - }, - ); - }} - > - -
- )} -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/client/main-view/Tabs/TabsTemplateEditor.tsx b/twake/frontend/src/app/views/client/main-view/Tabs/TabsTemplateEditor.tsx deleted file mode 100644 index 21f3ae45..00000000 --- a/twake/frontend/src/app/views/client/main-view/Tabs/TabsTemplateEditor.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Button, Row, Input, Select } from 'antd'; - -import { Application, AppType } from 'app/features/applications/types/application'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import { TabType } from 'app/features/tabs/types/tab'; -import Icon from 'app/components/icon/icon'; -import ModalManager from 'app/components/modal/modal-manager'; -import ObjectModal from 'components/object-modal/object-modal'; -import WorkspacesApps from 'app/deprecated/workspaces/workspaces_apps.jsx'; -import Languages from 'app/features/global/services/languages-service'; -import { getCompanyApplications } from 'app/features/applications/state/company-applications'; -import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; -import { useCurrentCompany } from 'app/features/companies/hooks/use-companies'; - -const { Option } = Select; - -type PropsType = { - tab?: TabType; - onChangeTabs: (tab: TabType) => void; - currentUserId?: string; -}; - -export default (props: PropsType): JSX.Element => { - const [appId, setAppId] = useState(props.tab?.application_id || ''); - const [tabName, setTabName] = useState(props.tab?.name || ''); - const [, setWorkspacesApps] = useState([]); - const { company } = useCurrentCompany(); - - const { applications: companyApplications } = useCompanyApplications(company?.id || ''); - - useEffect(() => { - generateWorkspacesApps(); - }, []); - - const generateWorkspacesApps = () => { - const apps: AppType[] = getCompanyApplications(Groups.currentGroupId); - return setWorkspacesApps(apps); - }; - - return ( - { - let editedTab: TabType | undefined = props.tab; - - ModalManager.closeAll(); - - if (editedTab?.id) { - editedTab = { - ...editedTab, - name: tabName, - }; - } else { - editedTab = { - name: tabName, - order: '' + new Date().getTime(), - owner: props.currentUserId, - application_id: appId, - configuration: {}, - }; - } - - return props.onChangeTabs(editedTab); - }} - > - {Languages.t(props.tab?.id ? 'general.edit' : 'general.create')} - - } - > -
- - setTabName(e.target.value)} - className="medium full_width bottom-margin" - type="text" - placeholder="Tab name" - /> - - {!props.tab?.id && ( - - - - )} -
-
- ); -}; diff --git a/twake/frontend/src/app/views/client/popup/UserParameter/UserParameter.jsx b/twake/frontend/src/app/views/client/popup/UserParameter/UserParameter.jsx index be3841b3..121e1c18 100755 --- a/twake/frontend/src/app/views/client/popup/UserParameter/UserParameter.jsx +++ b/twake/frontend/src/app/views/client/popup/UserParameter/UserParameter.jsx @@ -90,7 +90,7 @@ export default class UserParameter extends Component {
{this.state.i18n.t('scenes.apps.account.title')}
{this.canEditAccount() && ( <> - {InitService.server_infos?.configuration?.accounts?.type === 'console' && ( + {InitService.server_infos?.configuration?.accounts?.type === 'remote' && ( <> {Languages.t('scenes.apps.account.on_console')} + + )} + {!(inTrash || access === 'read') && ( + <> + + + + )} + + ); +}; diff --git a/twake/frontend/src/app/views/client/side-bar/index.tsx b/twake/frontend/src/app/views/client/side-bar/index.tsx new file mode 100644 index 00000000..12bcb6b2 --- /dev/null +++ b/twake/frontend/src/app/views/client/side-bar/index.tsx @@ -0,0 +1,110 @@ +import { Button } from '@atoms/button/button'; +import { + ClockIcon, + CloudIcon, + ExternalLinkIcon, + HeartIcon, + PlusIcon, + ShareIcon, + TrashIcon, + UploadIcon, +} from '@heroicons/react/outline'; +import { useRecoilState } from 'recoil'; +import { Title } from '../../../atoms/text'; +import { useDriveItem } from '../../../features/drive/hooks/use-drive-item'; +import { DriveCurrentFolderAtom } from '../body/drive/browser'; +import Account from '../common/account'; +import AppGrid from '../common/app-grid'; +import DiskUsage from '../common/disk-usage'; +import Actions from './actions'; + +export default () => { + const [parentId, setParentId] = useRecoilState(DriveCurrentFolderAtom('root')); + const active = false; + const { access: rootAccess } = useDriveItem('root'); + const { inTrash } = useDriveItem(parentId); + const activeClass = 'bg-zinc-50 dark:bg-zinc-800 !text-blue-500'; + return ( +
+
+
+
+
+ +
+ +
+ +
+ Actions +
+ + + +
+ Drive + + {false && ( + <> + + + + )} + {rootAccess === 'manage' && ( + + )} + + {false && ( + <> +
+ Shared + + + + )} +
+ +
+ +
+
+ ); +}; diff --git a/twake/frontend/src/app/views/client/side-bars.tsx b/twake/frontend/src/app/views/client/side-bars.tsx deleted file mode 100755 index f1801e87..00000000 --- a/twake/frontend/src/app/views/client/side-bars.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-use-before-define -import { Layout } from 'antd'; - -import WorkspacesBar from './workspaces-bar'; - -import { useCurrentCompanyRealtime } from '../../features/companies/hooks/use-companies'; - -import { useNotifications } from 'app/features/users/hooks/use-notifications'; -import { usePreloadSomeUsers } from 'app/features/users/hooks/use-user-list'; -import './workspaces-bar/styles.scss'; - -export default () => { - useCurrentCompanyRealtime(); - - // We call this hook here to be able to preload some users in user list state - usePreloadSomeUsers(); - useNotifications(); - - return ( - - - - ); -}; diff --git a/twake/frontend/src/app/views/client/styles.scss b/twake/frontend/src/app/views/client/styles.scss deleted file mode 100755 index 1dfe6c9e..00000000 --- a/twake/frontend/src/app/views/client/styles.scss +++ /dev/null @@ -1,69 +0,0 @@ -.appPage { - display: flex; - flex-direction: row; - width: 100vw; - height: 100%; - position: absolute; - top: 0; - left: 0; - background: #fff; -} - -.twake_bad_device { - width: 100%; - height: 100%; - background: #fff; - text-align: center; - overflow: hidden; - box-sizing: border-box; - overflow-y: auto; - overflow-x: hidden; - - .top, - .bottom { - padding: 20px; - } - .bottom { - z-index: 2; - position: relative; - width: 100%; - box-sizing: border-box; - } - - .top { - background: var(--primary); - height: 50vh; - position: relative; - &:after { - content: ''; - width: 200%; - height: 270px; - background: #ffffff; - position: absolute; - transform: translateX(-50%) rotateZ(-4deg); - bottom: -200px; - } - .title { - position: absolute; - width: calc(100% - 40px); - margin: auto; - top: 0; - bottom: 0; - height: 150px; - } - } - - .title { - margin-bottom: 40px; - color: var(--white); - } - - .subtitle { - margin-bottom: 40px; - } - - .store_img { - height: 48px; - margin: 8px; - } -} diff --git a/twake/frontend/src/app/views/applications/viewer/archive/controls.tsx b/twake/frontend/src/app/views/client/viewer/archive/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/archive/controls.tsx rename to twake/frontend/src/app/views/client/viewer/archive/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/archive/display.tsx b/twake/frontend/src/app/views/client/viewer/archive/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/archive/display.tsx rename to twake/frontend/src/app/views/client/viewer/archive/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/code/controls.tsx b/twake/frontend/src/app/views/client/viewer/code/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/code/controls.tsx rename to twake/frontend/src/app/views/client/viewer/code/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/code/display.tsx b/twake/frontend/src/app/views/client/viewer/code/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/code/display.tsx rename to twake/frontend/src/app/views/client/viewer/code/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/controls.tsx b/twake/frontend/src/app/views/client/viewer/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/controls.tsx rename to twake/frontend/src/app/views/client/viewer/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/display.tsx b/twake/frontend/src/app/views/client/viewer/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/display.tsx rename to twake/frontend/src/app/views/client/viewer/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/drive-display.tsx b/twake/frontend/src/app/views/client/viewer/drive-display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/drive-display.tsx rename to twake/frontend/src/app/views/client/viewer/drive-display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/drive-preview.tsx b/twake/frontend/src/app/views/client/viewer/drive-preview.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/drive-preview.tsx rename to twake/frontend/src/app/views/client/viewer/drive-preview.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/images/controls.tsx b/twake/frontend/src/app/views/client/viewer/images/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/images/controls.tsx rename to twake/frontend/src/app/views/client/viewer/images/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/images/display.tsx b/twake/frontend/src/app/views/client/viewer/images/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/images/display.tsx rename to twake/frontend/src/app/views/client/viewer/images/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/other/controls.tsx b/twake/frontend/src/app/views/client/viewer/other/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/other/controls.tsx rename to twake/frontend/src/app/views/client/viewer/other/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/other/display.tsx b/twake/frontend/src/app/views/client/viewer/other/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/other/display.tsx rename to twake/frontend/src/app/views/client/viewer/other/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/other/editors-service.ts b/twake/frontend/src/app/views/client/viewer/other/editors-service.ts similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/other/editors-service.ts rename to twake/frontend/src/app/views/client/viewer/other/editors-service.ts diff --git a/twake/frontend/src/app/views/applications/viewer/pdf/controls.tsx b/twake/frontend/src/app/views/client/viewer/pdf/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/pdf/controls.tsx rename to twake/frontend/src/app/views/client/viewer/pdf/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/pdf/display.tsx b/twake/frontend/src/app/views/client/viewer/pdf/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/pdf/display.tsx rename to twake/frontend/src/app/views/client/viewer/pdf/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/videos/controls.tsx b/twake/frontend/src/app/views/client/viewer/videos/controls.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/videos/controls.tsx rename to twake/frontend/src/app/views/client/viewer/videos/controls.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/videos/display.tsx b/twake/frontend/src/app/views/client/viewer/videos/display.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/videos/display.tsx rename to twake/frontend/src/app/views/client/viewer/videos/display.tsx diff --git a/twake/frontend/src/app/views/applications/viewer/viewer.tsx b/twake/frontend/src/app/views/client/viewer/viewer.tsx similarity index 100% rename from twake/frontend/src/app/views/applications/viewer/viewer.tsx rename to twake/frontend/src/app/views/client/viewer/viewer.tsx diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/index.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/index.tsx deleted file mode 100644 index eebff3ac..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/index.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { ReactNode } from 'react'; -import classNames from 'classnames'; -import Menu from 'components/menus/menu'; -import { addApiUrlIfNeeded } from 'app/features/global/utils/URLUtils'; -import { useCurrentCompany } from 'app/features/companies/hooks/use-companies'; -import RouterService from 'app/features/router/services/router-service'; -import Languages from 'app/features/global/services/languages-service'; -import PopupService from 'app/deprecated/popupManager/popupManager.js'; - -import './styles.scss'; -import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; -import { - useCompanyNotifications, - useOtherCompanyNotifications, -} from 'app/features/users/hooks/use-notifications'; -import menusManager from 'app/components/menus/menus-manager'; -import { UserCompanyType } from 'app/features/users/types/user'; - -type MenuObjectType = { [key: string]: unknown }; - -export default ({ - children, - withCompanyName = true, -}: { - children?: ReactNode; - withCompanyName?: boolean; -}) => { - const { user } = useCurrentUser(); - - return ( - c.company) - .sort((a, b) => a.name.localeCompare(b.name)) - .map(c => ({ - type: 'react-element', - reactElement: ( -
{ - PopupService.closeAll(); - menusManager.closeMenu(); - RouterService.push( - RouterService.generateRouteFromState( - { - companyId: c.id, - }, - { replace: true }, - ), - ); - }} - style={{ display: 'flex' }} - > - -
- ), - key: c.id, - })), - ]} - position="top" - > - {!children && } - {!!children && children} -
- ); -}; - -export const CurrentCompanyLogo = ({ - size, - withCompanyName = true, - showBadge = false, -}: { - size?: number; - withCompanyName?: boolean; - showBadge?: boolean; -}) => { - const { company } = useCurrentCompany(); - const { badges } = useOtherCompanyNotifications(company?.id || ''); - - if (!company) { - return <>; - } - - return ( -
- {showBadge && badges.length > 0 &&
} - -
- {`${company.name}-`[0].toUpperCase()} -
- {withCompanyName ?
{company.name}
: <>} -
- ); -}; - -const CompanyInMenu = (props: { company: UserCompanyType['company'] }) => { - const c = props.company; - const { badges } = useCompanyNotifications(c.id || ''); - - return ( - <> -
-
- {`${c.name}-`[0].toUpperCase()} -
-
- - {c.name} - - {badges.length > 0 &&
{Math.max(1, badges.length)}
} - - ); -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/styles.scss b/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/styles.scss deleted file mode 100644 index 222f6bde..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/CompanySelector/styles.scss +++ /dev/null @@ -1,88 +0,0 @@ -.company-selector-container { - width: 100%; - position: relative; - transition: top 0.2s, height 0.2s, opacity 0.2s; - cursor: pointer; - - .image { - color: var(--secondary); - border-radius: var(--border-radius-base); - - background-color: var(--white); - background-size: cover; - background-position: center; - - display: flex !important; - justify-content: center; - align-items: center; - - font-size: 14px; - text-align: center; - width: 32px; - height: 32px; - line-height: 32px; - margin: auto; - margin-bottom: 8px; - background-size: cover; - background-position: center; - font-weight: 500; - overflow: visible; - - &.has_image { - font-size: 0px; - } - } - - .name { - color: var(--white); - opacity: 1; - text-align: center; - font-size: 12px; - height: 16px; - margin-right: 5%; - margin-left: 5%; - font-weight: 500; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - text-transform: capitalize; - } - - .notification_dot { - position: absolute; - top: -8px; - left: calc(50% + 8px); - background: var(--red); - width: 16px; - height: 16px; - border: 2px solid var(--secondary); - box-sizing: border-box; - border-radius: 50%; - } -} - -.company-in-menu { - margin: -8px !important; - - .text { - line-height: 32px; - flex: 1; - } - - .notification_dot { - min-width: 16px; - height: 16px; - color: var(--white); - background: var(--red); - font-size: 10px; - font-weight: 700; - text-align: center; - line-height: 16px; - border-radius: var(--border-radius-small); - margin: 8px 0px; - padding: 0 2px; - vertical-align: top; - box-sizing: border-box; - display: inline-block; - } -} diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/GotALink.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/GotALink.tsx deleted file mode 100644 index 9587d02d..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/GotALink.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Button, Col, Row, Input } from 'antd'; -import React from 'react'; - -export default () => { - const [isInvitationLink, setIsInvitationLink] = React.useState(false); - const [invitationLink, setInvitationLink] = React.useState(''); - return ( -
- setIsInvitationLink(true)}> - Got an invitation link ? - - {isInvitationLink && ( -
- - - setInvitationLink(e.target.value)} - /> - - - - - -
- )} -
- ); -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoCompanies.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoCompanies.tsx deleted file mode 100644 index 6df0cde6..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoCompanies.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { Component } from 'react'; -import Emojione from 'components/emojione/emojione'; -import LoginService from 'app/features/auth/login-service'; -import Languages from 'app/features/global/services/languages-service'; -import Groups from 'app/deprecated/workspaces/groups.js'; -import Workspaces from 'app/deprecated/workspaces/workspaces.jsx'; -import GotALink from './GotALink'; - -import './styles.scss'; - -export default class WelcomePage extends Component { - private retrying = false; - constructor(props: unknown) { - super(props); - this.state = { - i18n: Languages, - workspaces: Workspaces, - group: Groups, - page: 1, - }; - Languages.addListener(this); - Workspaces.addListener(this); - Groups.addListener(this); - } - componentWillUnmount() { - Languages.removeListener(this); - Workspaces.removeListener(this); - Groups.removeListener(this); - } - retry() { - if (!this.retrying) { - this.retrying = true; - LoginService.init(); - setTimeout(() => { - this.retrying = false; - }); - } - } - - render() { - return ( -
-
-
-
- {' '} - {Languages.t('scenes.app.workspaces.welcome_page.welcome_header')} -
-
- {Languages.t('scenes.app.workspaces.welcome_page.no_company_subtitle')}{' '} - -
- - - - -
-
-
- ); - } -} diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoWorkspaces.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoWorkspaces.tsx deleted file mode 100644 index 8d76ab22..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/NoWorkspaces.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { Suspense } from 'react'; -import LoginService from 'app/features/auth/login-service'; -import Languages from 'app/features/global/services/languages-service'; -import CompanySelector, { CurrentCompanyLogo } from '../CompanySelector/index'; -import { Button } from 'antd'; -import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; -import { useCurrentCompany } from 'app/features/companies/hooks/use-companies'; -import InitService from 'app/features/global/services/init-service'; -import GotALink from './GotALink'; - -import './styles.scss'; - -export default () => { - const retry = () => { - document.location.reload(); - }; - const { company } = useCurrentCompany(); - - return ( - }> -
-
-
-
- - {Languages.t('scenes.app.workspaces.welcome_page.added_to_company')}{' '} - {company?.name}. -
- {Languages.t('scenes.app.workspaces.welcome_page.no_workspace_subtitle')} -
- - - -
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - retry()}> - {Languages.t('scenes.app.workspaces.welcome_page.try_again')} - -        - {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - LoginService.logout()}> - {Languages.t('scenes.apps.account.account.logout')} - -
- -
-
- - {InitService.server_infos?.configuration?.accounts?.type === 'console' && ( - - )} -
-
-
-
- ); -}; - -export const ChangeCompany = () => { - const { user } = useCurrentUser(); - - if (user?.companies?.length === 0) { - return <>; - } - - return ( -
- - - -
- ); -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/styles.scss b/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/styles.scss deleted file mode 100644 index 9936ff85..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/NoWorkspaces/styles.scss +++ /dev/null @@ -1,69 +0,0 @@ -.welcomePage { - text-align: center; - width: 600px; - padding: 40px; - margin: auto; - margin-top: 20vh; - - .company-selector-container .image { - box-shadow: 0 0 0 2px var(--secondary); - } - - .title { - font-size: 40px; - .emojione { - margin-right: 5px; - width: 64px; - height: 64px; - bottom: 8px; - } - } - .subtitle { - text-align: center; - line-height: 25px; - margin: auto; - margin-top: 30px; - margin-bottom: 40px; - max-width: 500px; - } - .signed { - text-align: right; - opacity: 0.7; - font-size: 13px; - } - .btn { - height: 40px; - line-height: 40px; - border-radius: var(--border-radius-base); - display: inline-block; - padding: 0px 20px; - } - .blueBtn { - background: var(--primary); - color: white; - } - .btn:hover { - cursor: pointer; - box-shadow: inset 0px 0px 100px 100px rgba(255, 255, 255, 0.2); - } - .retry { - border: none; - margin-top: 40px; - - .link { - color: #888; - } - } - .otherMail { - border: none; - margin-top: 40px; - line-height: 20px; - - .label { - border: none; - } - .link { - border: none; - } - } -} diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.scss b/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.scss deleted file mode 100755 index b7433f4c..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.scss +++ /dev/null @@ -1,130 +0,0 @@ -.workspace { - width: 100%; - margin-bottom: 24px; - position: relative; - transition: top 0.2s, height 0.2s, opacity 0.2s; - cursor: pointer; - - .image { - transition: background 0.2s; - } - .name { - transition: color 0.2s; - } - - &:hover, - &.is_selected { - .image { - background-color: var(--white); - } - .name { - color: var(--white); - } - &::after { - background: var(--white); - } - } - - .notification_dot { - position: absolute; - top: -8px; - left: calc(50% + 8px); - background: var(--red); - width: 16px; - height: 16px; - border: 2px solid var(--secondary); - box-sizing: border-box; - border-radius: 50%; - } - - //Selector left indicator - &::after { - content: ''; - width: 4px; - border-radius: 0 2px 2px 0; - height: 4px; - top: 14px; - position: absolute; - left: 0px; - background: var(--grey-dark); - opacity: 0; - transition: top 0.2s, height 0.2s, background 0.2s; - } - &.is_selected { - &::after { - height: 32px !important; - top: 0px !important; - opacity: 1; - background: var(--white); - } - } - &.has_notifications { - &::after { - height: 8px; - top: 12px; - opacity: 1; - border-radius: 0 4px 4px 0; - } - } - - .image { - background-color: var(--grey-dark); - background-size: cover; - background-position: center; - color: var(--secondary); - font-size: 14px; - text-align: center; - width: 32px; - height: 32px; - line-height: 32px; - border-radius: var(--border-radius-base); - margin: auto; - margin-bottom: 8px; - background-size: cover; - background-position: center; - font-weight: 500; - overflow: visible; - - &.has_image { - font-size: 0px; - } - } - - .name { - color: var(--grey-dark); - opacity: 1; - text-align: center; - font-size: 12px; - height: 16px; - margin-right: 5%; - margin-left: 5%; - font-weight: 500; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - text-transform: capitalize; - } -} - -.loading_render { - .workspace { - pointer-events: none; - &::after, - .notification_dot { - opacity: 0 !important; - } - .image { - font-size: 0px; - background: var(--grey-dark) !important; - } - .name { - background: var(--grey-dark); - border-radius: var(--border-radius-large); - width: 32px; - margin: auto; - height: 12px; - font-size: 0px; - margin-bottom: 28px; - } - } -} diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.tsx deleted file mode 100755 index c81924fe..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/Workspace.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-use-before-define -import React from 'react'; - -import WorkspacesService from 'app/deprecated/workspaces/workspaces'; -import WorkspaceIcon from './WorkspaceIcon'; -import { WorkspaceType } from 'app/features/workspaces/types/workspace'; -import useRouterWorkspaceSelected from 'app/features/router/hooks/use-router-workspace-selected'; - -import './Workspace.scss'; - -type Props = { - workspace: WorkspaceType; -}; - -export default ({ workspace }: Props): JSX.Element => { - return ( - WorkspacesService.select(workspace)} - /> - ); -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/WorkspaceIcon.tsx b/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/WorkspaceIcon.tsx deleted file mode 100755 index 2f58e159..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/components/Workspace/WorkspaceIcon.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-use-before-define -import React from 'react'; -import classNames from 'classnames'; -import { Skeleton } from 'antd'; - -import { addApiUrlIfNeeded } from 'app/features/global/utils/URLUtils'; -import { WorkspaceType } from 'app/features/workspaces/types/workspace'; -import { useWorkspaceNotifications } from 'app/features/users/hooks/use-notifications'; - -type Props = { - workspace: WorkspaceType; - selected: boolean; - onClick: () => void; -}; - -export default (props: Props): JSX.Element => { - const { badges } = useWorkspaceNotifications(props.workspace.id || ''); - const unreadInWorkspace = badges.length; - const workspace = props.workspace || {}; - const name = workspace.mininame || workspace.name || ''; - - return ( -
0, - })} - onClick={props.onClick} - > -
- {workspace.name !== '' && `${name}-`[0].toUpperCase()} - {unreadInWorkspace > 0 &&
} -
-
{name}
-
- ); -}; - -export const LoadingWorkspaceIcon = () => { - const size = 32; - const shape = 'square'; - return ; -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/index.tsx b/twake/frontend/src/app/views/client/workspaces-bar/index.tsx deleted file mode 100755 index 2d2eb003..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect } from 'react'; -import PerfectScrollbar from 'react-perfect-scrollbar'; -import { Layout, Skeleton } from 'antd'; - -import { WorkspaceType } from 'app/features/workspaces/types/workspace'; -import Workspace from './components/Workspace/Workspace'; -import { useWorkspaces } from 'app/features/workspaces/hooks/use-workspaces'; -import useRouterCompany from 'app/features/router/hooks/use-router-company'; -import { LoadingWorkspaceIcon } from './components/Workspace/WorkspaceIcon'; -import CompanySelector from './components/CompanySelector'; - -import './styles.scss'; - -export default () => { - const companyId = useRouterCompany(); - - return ( - - {companyId && } - - - ); -}; - -const WorkspaceListComponent = ({ companyId }: { companyId: string }) => { - const { workspaces, refresh } = useWorkspaces(companyId); - - useEffect(() => { - companyId && refresh(); - }, [companyId]); - - return ( - - {workspaces.map((ws: WorkspaceType) => ( - - ))} - - ); -}; - -export const LoadingWorkspaceBar = () => { - return ( - -
- - -
- -
- ); -}; diff --git a/twake/frontend/src/app/views/client/workspaces-bar/styles.scss b/twake/frontend/src/app/views/client/workspaces-bar/styles.scss deleted file mode 100755 index b6f94a7b..00000000 --- a/twake/frontend/src/app/views/client/workspaces-bar/styles.scss +++ /dev/null @@ -1,121 +0,0 @@ -.has_workspaces.animated { - .workspaces_view { - animation: init_workspaces_view_60 ease 0.2s; - } -} -.workspaces_view { - background: var(--secondary); - width: 60px; - min-width: 60px; - overflow: hidden; - padding-bottom: 8px; - transition: width 0.2s; - div { - display: flex; - flex-direction: column; - align-items: center; - .list { - padding-top: 20px; - width: 100%; - - &:not(.ps--scrolling-y) { - .ps__rail-y { - display: none; - } - } - } - } -} - -.workpaces_view_loading { - padding-bottom: 30px; - div { - display: flex; - flex-direction: column; - align-items: center; - .list { - position: relative; - height: 100%; - padding-top: 20px; - } - } -} - -.no_workspaces { - .workspaces_view { - width: 0px !important; - min-width: 0px !important; - } -} - -.electron { - .workspaces_view { - width: 70px; - .list { - padding-top: 30px; - } - } - .has_workspaces.animated { - .workspaces_view { - animation: init_workspaces_view_70 ease 0.2s; - } - } -} - -.group_in_selector { - position: relative; - margin: -8px !important; - - &.menu .icon { - .letter, - .emojione { - width: 20px; - height: 20px; - line-height: 20px; - font-size: 13px; - margin: 0px; - } - } - - .group_notification { - .notification, - .badge { - position: absolute; - right: 8px; - top: 8px; - margin-top: 0px !important; - margin-left: 5px; - background: var(--red); - width: 16px; - height: 16px; - border-radius: var(--border-radius-large); - color: var(--white); - font-size: 12px; - line-height: 16px; - text-align: center; - min-width: 16px; - - &.circle { - margin-top: 7px; - } - } - } -} - -@keyframes init_workspaces_view_60 { - 0% { - width: 0px; - } - 100% { - width: 60px; - } -} - -@keyframes init_workspaces_view_70 { - 0% { - width: 0px; - } - 100% { - width: 70px; - } -} diff --git a/twake/frontend/src/app/views/integration/components/apps.jsx b/twake/frontend/src/app/views/integration/components/apps.jsx deleted file mode 100644 index b330e779..00000000 --- a/twake/frontend/src/app/views/integration/components/apps.jsx +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable react/prop-types */ -import React from 'react'; -import AppsIcon from '@material-ui/icons/Apps'; -import Popover from '@material-ui/core/Popover'; -import IconButton from '@material-ui/core/IconButton'; - -export default function Apps(props) { - const [anchorEl, setAnchorEl] = React.useState(null); - - const handleClick = event => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - const id = open ? 'simple-popover' : undefined; - - return ( -
- - - - -
- {props.apps.map(item => ( - - - -
- ); -} diff --git a/twake/frontend/src/app/views/integration/index.tsx b/twake/frontend/src/app/views/integration/index.tsx deleted file mode 100644 index aa2fae24..00000000 --- a/twake/frontend/src/app/views/integration/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; - -import InitService from 'app/features/global/services/init-service'; -import Apps from './components/apps.jsx'; - -import './styles.scss'; - -export default (props: { children: React.ReactNode }): JSX.Element => { - const server_infos = InitService.useWatcher( - () => InitService.server_infos_loaded && InitService.server_infos, - ); - - if (!server_infos) { - return <>; - } - - const branding = (server_infos && server_infos?.configuration?.branding) || {}; - - if (!(branding || {}).name) { - return <>{props.children}; - } - - if (branding.style && branding.style.color) { - document.documentElement.style.setProperty('--primary', branding.style.color); - document.documentElement.style.setProperty( - '--primary-background', - branding.style.color.substr(0, 7) + '22', - ); - document.documentElement.style.setProperty( - '--primary-hover', - branding.style.color.substr(0, 7) + 'AA', - ); - document.documentElement.style.setProperty('--secondary', '#38383d'); - } - if (branding.style && branding.style.default_border_radius) { - document.documentElement.style.setProperty( - '--border-radius-base', - branding.style.default_border_radius + 'px', - ); - } - - return ( -
- {branding.header && ( -
- {(branding.header.logo || branding.logo) && ( -
- )} -
- -
-
- )} -
{props.children}
-
- ); -}; diff --git a/twake/frontend/src/app/views/integration/styles.scss b/twake/frontend/src/app/views/integration/styles.scss deleted file mode 100644 index 56338ce2..00000000 --- a/twake/frontend/src/app/views/integration/styles.scss +++ /dev/null @@ -1,61 +0,0 @@ -.integration { - display: flex; - flex-direction: column; - height: 100vh; - width: 100vw; - - & > .integration-header { - padding: 8px; - transition: height 0.2s; - background: var(--primary); - display: flex; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - z-index: 1; - - svg { - color: #fff; - } - - .logo { - flex: 1; - background-position: left; - background-repeat: no-repeat; - background-size: auto 60%; - } - } - - & > .integrated-app { - flex: 1; - overflow: hidden; - - & > .appPage { - height: 100%; - position: initial; - } - } -} - -.integration-apps { - max-width: 290px; - margin-bottom: 8px; - .app { - opacity: 0.9; - display: inline-block; - text-align: center; - color: initial; - font-size: 12px; - font-weight: 500; - margin: 8px; - - &:hover { - opacity: 1; - } - - .image { - width: 80px; - height: 80px; - background-size: contain; - background-position: center; - } - } -} diff --git a/twake/frontend/src/app/views/join/index.tsx b/twake/frontend/src/app/views/join/index.tsx index 235c09fb..2d7cd1af 100644 --- a/twake/frontend/src/app/views/join/index.tsx +++ b/twake/frontend/src/app/views/join/index.tsx @@ -29,7 +29,7 @@ export default (): JSX.Element => { const [error, setError] = useState(); const [info, setInfo] = useState(); const [busy, setBusy] = useState(false); - const [,setCookie] = useCookies(['pending-redirect']); + const [, setCookie] = useCookies(['pending-redirect']); if (info?.company?.plan) FeatureTogglesService.setFeaturesFromCompanyPlan(info?.company?.plan); @@ -67,7 +67,7 @@ export default (): JSX.Element => { const currentPage = document.location.href; const authUrl = `${ - InitService.server_infos?.configuration?.accounts?.console?.authority + InitService.server_infos?.configuration?.accounts?.remote?.authority }/oauth2/authorize?invite=1&redirect_uri=${encodeURIComponent(origin)}`; setCookie('pending-redirect', currentPage, { path: '/', maxAge: 60 * 60 }); setBusy(true); @@ -92,7 +92,7 @@ export default (): JSX.Element => { }; const onCreateCompanyBtnClick = () => { - if (InitService.server_infos?.configuration?.accounts?.type === 'console') { + if (InitService.server_infos?.configuration?.accounts?.type === 'remote') { return document.location.replace(InitService.getConsoleLink('account_management_url')); } else { document.location.replace('/'); diff --git a/twake/frontend/src/app/views/login/internal/internal-login.tsx b/twake/frontend/src/app/views/login/internal/internal-login.tsx index d3412c26..68a608d7 100755 --- a/twake/frontend/src/app/views/login/internal/internal-login.tsx +++ b/twake/frontend/src/app/views/login/internal/internal-login.tsx @@ -7,7 +7,7 @@ import InitService from 'app/features/global/services/init-service'; import LoginService from 'app/features/auth/login-service'; import Icon from 'app/components/icon/icon.jsx'; -import LoginView from './login-view/login-view.jsx'; +import LoginView from './login-view/login-view'; import Signin from './signin/signin.jsx'; import VerifyMail from './verify-mail/verify-mail.jsx'; import ForgotPassword from './forgot-password/index.jsx'; diff --git a/twake/frontend/src/app/views/login/login.tsx b/twake/frontend/src/app/views/login/login.tsx index 7fd6a002..3bc95bec 100644 --- a/twake/frontend/src/app/views/login/login.tsx +++ b/twake/frontend/src/app/views/login/login.tsx @@ -8,7 +8,7 @@ const ConsoleLogin = lazy(() => import('app/views/login/console/console-login')) export default () => ( }> - {InitService.server_infos?.configuration?.accounts.type === 'console' ? ( + {InitService.server_infos?.configuration?.accounts.type === 'remote' ? ( ) : ( diff --git a/twake/frontend/tailwind.config.js b/twake/frontend/tailwind.config.js index 988f5089..c700e62d 100644 --- a/twake/frontend/tailwind.config.js +++ b/twake/frontend/tailwind.config.js @@ -1,7 +1,7 @@ const defaultTheme = require('tailwindcss/defaultTheme'); let shades = []; -['zink', 'red', 'orange', 'green', 'blue'].map(color => { +['zinc', 'red', 'orange', 'green', 'blue'].map(color => { [50, 100, 200, 300, 400, 500, 600, 700, 800, 900].map(shade => { shades.push(`bg-${color}-${shade}`); shades.push(`border-${color}-${shade}`); @@ -14,7 +14,7 @@ let shades = []; module.exports = { content: ['./src/**/*.{html,js,ts,tsx}'], - darkMode: 'class', + darkMode: 'media', //Use browser configuration theme: { extend: { fontFamily: { @@ -27,10 +27,10 @@ module.exports = { sm: '8px', }, fontSize: { - lg: '18px', - base: '16px', - sm: '14px', - xs: '12px', + lg: '17px', + base: '15px', + sm: '13px', + xs: '11px', }, colors: {