diff --git a/admin/components/instance/setup-form.tsx b/admin/components/instance/setup-form.tsx
index 77bf8e562..aa2350894 100644
--- a/admin/components/instance/setup-form.tsx
+++ b/admin/components/instance/setup-form.tsx
@@ -319,6 +319,8 @@ export const InstanceSetupForm: FC = (props) => {
handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
checked={formData.is_telemetry_enabled}
diff --git a/apiserver/plane/authentication/provider/oauth/gitlab.py b/apiserver/plane/authentication/provider/oauth/gitlab.py
index a251e5f8b..3795cc37f 100644
--- a/apiserver/plane/authentication/provider/oauth/gitlab.py
+++ b/apiserver/plane/authentication/provider/oauth/gitlab.py
@@ -16,49 +16,37 @@ from plane.authentication.adapter.error import (
class GitLabOAuthProvider(OauthAdapter):
- (GITLAB_HOST,) = get_configuration_value(
- [
- {
- "key": "GITLAB_HOST",
- "default": os.environ.get("GITLAB_HOST", "https://gitlab.com"),
- },
- ]
- )
-
- if not GITLAB_HOST:
- raise AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["GITLAB_NOT_CONFIGURED"],
- error_message="GITLAB_NOT_CONFIGURED",
- )
-
- host = GITLAB_HOST
-
- token_url = (
- f"{host}/oauth/token"
- )
- userinfo_url = (
- f"{host}/api/v4/user"
- )
-
provider = "gitlab"
scope = "read_user"
def __init__(self, request, code=None, state=None, callback=None):
- GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET = get_configuration_value(
- [
- {
- "key": "GITLAB_CLIENT_ID",
- "default": os.environ.get("GITLAB_CLIENT_ID"),
- },
- {
- "key": "GITLAB_CLIENT_SECRET",
- "default": os.environ.get("GITLAB_CLIENT_SECRET"),
- },
- ]
+ GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET, GITLAB_HOST = (
+ get_configuration_value(
+ [
+ {
+ "key": "GITLAB_CLIENT_ID",
+ "default": os.environ.get("GITLAB_CLIENT_ID"),
+ },
+ {
+ "key": "GITLAB_CLIENT_SECRET",
+ "default": os.environ.get("GITLAB_CLIENT_SECRET"),
+ },
+ {
+ "key": "GITLAB_HOST",
+ "default": os.environ.get(
+ "GITLAB_HOST", "https://gitlab.com"
+ ),
+ },
+ ]
+ )
)
- if not (GITLAB_CLIENT_ID and GITLAB_CLIENT_SECRET):
+ self.host = GITLAB_HOST
+ self.token_url = f"{self.host}/oauth/token"
+ self.userinfo_url = f"{self.host}/api/v4/user"
+
+ if not (GITLAB_CLIENT_ID and GITLAB_CLIENT_SECRET and GITLAB_HOST):
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITLAB_NOT_CONFIGURED"],
error_message="GITLAB_NOT_CONFIGURED",
@@ -75,9 +63,7 @@ class GitLabOAuthProvider(OauthAdapter):
"scope": self.scope,
"state": state,
}
- auth_url = (
- f"{self.host}/oauth/authorize?{urlencode(url_params)}"
- )
+ auth_url = f"{self.host}/oauth/authorize?{urlencode(url_params)}"
super().__init__(
request,
self.provider,
@@ -98,7 +84,7 @@ class GitLabOAuthProvider(OauthAdapter):
"client_secret": self.client_secret,
"code": self.code,
"redirect_uri": self.redirect_uri,
- "grant_type": "authorization_code"
+ "grant_type": "authorization_code",
}
token_response = self.get_user_token(
data=data, headers={"Accept": "application/json"}
@@ -109,7 +95,8 @@ class GitLabOAuthProvider(OauthAdapter):
"refresh_token": token_response.get("refresh_token", None),
"access_token_expired_at": (
datetime.fromtimestamp(
- token_response.get("created_at") + token_response.get("expires_in"),
+ token_response.get("created_at")
+ + token_response.get("expires_in"),
tz=pytz.utc,
)
if token_response.get("expires_in")
diff --git a/apiserver/plane/utils/paginator.py b/apiserver/plane/utils/paginator.py
index 8bea4746a..3ea74bf9b 100644
--- a/apiserver/plane/utils/paginator.py
+++ b/apiserver/plane/utils/paginator.py
@@ -187,11 +187,11 @@ class OffsetPaginator:
class GroupedOffsetPaginator(OffsetPaginator):
- # Field mappers
+ # Field mappers - list m2m fields here
FIELD_MAPPER = {
"labels__id": "label_ids",
"assignees__id": "assignee_ids",
- "modules__id": "module_ids",
+ "issue_module__module_id": "module_ids",
}
def __init__(
@@ -205,8 +205,12 @@ class GroupedOffsetPaginator(OffsetPaginator):
):
# Initiate the parent class for all the parameters
super().__init__(queryset, *args, **kwargs)
+
+ # Set the group by field name
self.group_by_field_name = group_by_field_name
+ # Set the group by fields
self.group_by_fields = group_by_fields
+ # Set the count filter - this are extra filters that need to be passed to calculate the counts with the filters
self.count_filter = count_filter
def get_result(self, limit=50, cursor=None):
@@ -224,8 +228,11 @@ class GroupedOffsetPaginator(OffsetPaginator):
offset = cursor.offset * cursor.value
stop = offset + (cursor.value or limit) + 1
+ # Check if the offset is greater than the max offset
if self.max_offset is not None and offset >= self.max_offset:
raise BadPaginationError("Pagination offset too large")
+
+ # Check if the offset is less than 0
if offset < 0:
raise BadPaginationError("Pagination offset cannot be negative")
@@ -269,6 +276,8 @@ class GroupedOffsetPaginator(OffsetPaginator):
False,
queryset.filter(row_number__gte=stop).exists(),
)
+
+ # Add previous cursors
prev_cursor = Cursor(
limit,
page - 1,
@@ -305,7 +314,7 @@ class GroupedOffsetPaginator(OffsetPaginator):
)
def __get_total_queryset(self):
- # Get total queryset
+ # Get total items for each group
return (
self.queryset.values(self.group_by_field_name)
.annotate(
@@ -328,7 +337,6 @@ class GroupedOffsetPaginator(OffsetPaginator):
)
+ (1 if group.get("count") == 0 else group.get("count"))
)
-
return total_group_dict
def __get_field_dict(self):
@@ -353,7 +361,7 @@ class GroupedOffsetPaginator(OffsetPaginator):
# Grouping for m2m values
total_group_dict = self.__get_total_dict()
- # Preparing a dict to keep track of group IDs associated with each label ID
+ # Preparing a dict to keep track of group IDs associated with each entity ID
result_group_mapping = defaultdict(set)
# Preparing a dict to group result by group ID
grouped_by_field_name = defaultdict(list)
@@ -390,7 +398,7 @@ class GroupedOffsetPaginator(OffsetPaginator):
return processed_results
def __query_grouper(self, results):
- # Grouping for single values
+ # Grouping for values that are not m2m
processed_results = self.__get_field_dict()
for result in results:
group_value = str(result.get(self.group_by_field_name))
@@ -411,10 +419,11 @@ class GroupedOffsetPaginator(OffsetPaginator):
class SubGroupedOffsetPaginator(OffsetPaginator):
+ # Field mappers this are the fields that are m2m
FIELD_MAPPER = {
"labels__id": "label_ids",
"assignees__id": "assignee_ids",
- "modules__id": "module_ids",
+ "issue_module__module_id": "module_ids",
}
def __init__(
@@ -428,11 +437,18 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
*args,
**kwargs,
):
+ # Initiate the parent class for all the parameters
super().__init__(queryset, *args, **kwargs)
+
+ # Set the group by field name
self.group_by_field_name = group_by_field_name
self.group_by_fields = group_by_fields
+
+ # Set the sub group by field name
self.sub_group_by_field_name = sub_group_by_field_name
self.sub_group_by_fields = sub_group_by_fields
+
+ # Set the count filter - this are extra filters that need to be passed to calculate the counts with the filters
self.count_filter = count_filter
def get_result(self, limit=30, cursor=None):
@@ -441,13 +457,19 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
if cursor is None:
cursor = Cursor(0, 0, 0)
+ # get the minimum value
limit = min(limit, self.max_limit)
# Adjust the initial offset and stop based on the cursor and limit
queryset = self.queryset
+ # the current page
page = cursor.offset
+
+ # the offset
offset = cursor.offset * cursor.value
+
+ # the stop
stop = offset + (cursor.value or limit) + 1
if self.max_offset is not None and offset >= self.max_offset:
@@ -496,6 +518,8 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
False,
queryset.filter(row_number__gte=stop).exists(),
)
+
+ # Add previous cursors
prev_cursor = Cursor(
limit,
page - 1,
@@ -579,19 +603,24 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
subgroup = str(item[self.sub_group_by_field_name])
count = item["count"]
+ # Create a dictionary of group and sub group
if group not in total_sub_group_dict:
total_sub_group_dict[str(group)] = {}
+ # Create a dictionary of sub group
if subgroup not in total_sub_group_dict[group]:
total_sub_group_dict[str(group)][str(subgroup)] = {}
+ # Create a nested dictionary of group and sub group
total_sub_group_dict[group][subgroup] = count
return total_group_dict, total_sub_group_dict
def __get_field_dict(self):
+ # Create a field dictionary
total_group_dict, total_sub_group_dict = self.__get_total_dict()
+ # Create a dictionary of group and sub group
return {
str(group): {
"results": {
@@ -621,7 +650,6 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
result_id = result["id"]
group_id = result[self.group_by_field_name]
result_group_mapping[str(result_id)].add(str(group_id))
-
# Use the same calculation for the sub group
if self.sub_group_by_field_name in self.FIELD_MAPPER:
for result in results:
@@ -635,6 +663,9 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
group_value = str(result.get(self.group_by_field_name))
# Get the sub group value
sub_group_value = str(result.get(self.sub_group_by_field_name))
+ # Check if the group value is in the processed results
+ result_id = result["id"]
+
if (
group_value in processed_results
and sub_group_value
@@ -647,12 +678,14 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
[] if "None" in group_ids else group_ids
)
if self.sub_group_by_field_name in self.FIELD_MAPPER:
- sub_group_ids = list(result_group_mapping[str(result_id)])
- # for multi groups
- result[self.FIELD_MAPPER.get(self.group_by_field_name)] = (
- [] if "None" in sub_group_ids else sub_group_ids
+ sub_group_ids = list(
+ result_sub_group_mapping[str(result_id)]
)
-
+ # for multi groups
+ result[
+ self.FIELD_MAPPER.get(self.sub_group_by_field_name)
+ ] = ([] if "None" in sub_group_ids else sub_group_ids)
+ # If a result belongs to multiple groups, add it to each group
processed_results[str(group_value)]["results"][
str(sub_group_value)
]["results"].append(result)
@@ -677,8 +710,10 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
self.group_by_field_name in self.FIELD_MAPPER
or self.sub_group_by_field_name in self.FIELD_MAPPER
):
+ # if the grouping is done through m2m then
processed_results = self.__query_multi_grouper(results=results)
else:
+ # group it directly
processed_results = self.__query_grouper(results=results)
else:
processed_results = {}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 49a1e4443..0914e18fd 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -34,7 +34,8 @@
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",
"sonner": "^1.4.41",
- "tailwind-merge": "^2.0.0"
+ "tailwind-merge": "^2.0.0",
+ "use-font-face-observer": "^1.2.2"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.4.0",
diff --git a/packages/ui/src/emoji/icons-list.tsx b/packages/ui/src/emoji/icons-list.tsx
index 0352e1ec8..6575ae8f0 100644
--- a/packages/ui/src/emoji/icons-list.tsx
+++ b/packages/ui/src/emoji/icons-list.tsx
@@ -1,13 +1,15 @@
import React, { useEffect, useState } from "react";
+// icons
+import { Search } from "lucide-react";
+import { MATERIAL_ICONS_LIST } from "./icons";
+import { InfoIcon } from "../icons";
// components
import { Input } from "../form-fields";
+// hooks
+import useFontFaceObserver from "use-font-face-observer";
// helpers
import { cn } from "../../helpers";
import { DEFAULT_COLORS, TIconsListProps, adjustColorForContrast } from "./emoji-icon-helper";
-// icons
-import { MATERIAL_ICONS_LIST } from "./icons";
-import { InfoIcon } from "../icons";
-import { Search } from "lucide-react";
export const IconsList: React.FC = (props) => {
const { defaultColor, onChange } = props;
@@ -28,6 +30,15 @@ export const IconsList: React.FC = (props) => {
const filteredArray = MATERIAL_ICONS_LIST.filter((icon) => icon.name.toLowerCase().includes(query.toLowerCase()));
+ const isMaterialSymbolsFontLoaded = useFontFaceObserver([
+ {
+ family: `Material Symbols Rounded`,
+ style: `normal`,
+ weight: `normal`,
+ stretch: `condensed`,
+ },
+ ]);
+
return (
<>
@@ -118,12 +129,16 @@ export const IconsList: React.FC = (props) => {
});
}}
>
-
- {icon.name}
-
+ {isMaterialSymbolsFontLoaded ? (
+
+ {icon.name}
+
+ ) : (
+
+ )}
))}
diff --git a/web/core/components/common/logo.tsx b/web/core/components/common/logo.tsx
index b2c0b3d39..52d3dedb9 100644
--- a/web/core/components/common/logo.tsx
+++ b/web/core/components/common/logo.tsx
@@ -4,6 +4,7 @@ import { FC } from "react";
// emoji-picker-react
import { Emoji } from "emoji-picker-react";
// import { icons } from "lucide-react";
+import useFontFaceObserver from "use-font-face-observer";
import { TLogoProps } from "@plane/types";
// helpers
import { LUCIDE_ICONS_LIST } from "@plane/ui";
@@ -26,9 +27,29 @@ export const Logo: FC = (props) => {
const color = icon?.color;
const lucideIcon = LUCIDE_ICONS_LIST.find((item) => item.name === value);
+ const isMaterialSymbolsFontLoaded = useFontFaceObserver([
+ {
+ family: `Material Symbols Rounded`,
+ style: `normal`,
+ weight: `normal`,
+ stretch: `condensed`,
+ },
+ ]);
// if no value, return empty fragment
if (!value) return <>>;
+ if (!isMaterialSymbolsFontLoaded) {
+ return (
+
+ );
+ }
+
// emoji
if (in_use === "emoji") {
return ;
diff --git a/web/package.json b/web/package.json
index 068867902..f6b82b4ec 100644
--- a/web/package.json
+++ b/web/package.json
@@ -62,6 +62,7 @@
"swr": "^2.1.3",
"tailwind-merge": "^2.0.0",
"use-debounce": "^9.0.4",
+ "use-font-face-observer": "^1.2.2",
"uuid": "^9.0.0",
"zxcvbn": "^4.4.2"
},
diff --git a/yarn.lock b/yarn.lock
index 11a57223c..edda62dcd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4453,7 +4453,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@18.2.48", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48":
+"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.42", "@types/react@^18.2.48":
version "18.2.48"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1"
integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==
@@ -7714,6 +7714,11 @@ follow-redirects@^1.15.6:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
+fontfaceobserver@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.1.0.tgz#e2705d293e2c585a6531c2a722905657317a2991"
+ integrity sha512-ReOsO2F66jUa0jmv2nlM/s1MiutJx/srhAe2+TE8dJCMi02ZZOcCTxTCQFr3Yet+uODUtnr4Mewg+tNQ+4V1Ng==
+
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -10831,7 +10836,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-"prettier-fallback@npm:prettier@^3":
+"prettier-fallback@npm:prettier@^3", prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
version "3.3.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac"
integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==
@@ -10858,11 +10863,6 @@ prettier@^2.8.8:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
-prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.1.tgz#e68935518dd90bb7ec4821ba970e68f8de16e1ac"
- integrity sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==
-
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@@ -12294,16 +12294,7 @@ string-argv@~0.3.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12399,14 +12390,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13417,6 +13401,13 @@ use-debounce@^9.0.4:
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
+use-font-face-observer@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/use-font-face-observer/-/use-font-face-observer-1.2.2.tgz#ed230d907589c6b17e8c8b896c9f5913968ac5ed"
+ integrity sha512-5C11YC9vPQn5TeIKDvHHiUg59FBzV1LDIOjYJ2PVgn1raVoKHcuWf3dxVDb7OiqQVg3M2S1jX3LxbLw16xo8gg==
+ dependencies:
+ fontfaceobserver "2.1.0"
+
use-sidecar@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
@@ -13897,16 +13888,7 @@ workbox-window@6.6.1, workbox-window@^6.5.4:
"@types/trusted-types" "^2.0.2"
workbox-core "6.6.1"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==