Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artem Savchenko
2026-04-07 13:25:10 +07:00
50 changed files with 728 additions and 334 deletions
+1 -1
View File
@@ -310,7 +310,7 @@ services:
- PAYMENT_URL=http://huly.local:3040
- COMMUNICATION_API_ENABLED=true
- BACKUP_URL=http://huly.local:4039/api/backup,
- EXCLUDED_APPLICATIONS_FOR_ANONYMOUS=["chunter", "notification"]
- EXCLUDED_APPLICATIONS_FOR_ANONYMOUS=[]
# - DISABLE_SIGNUP=true
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
- PULSE_URL=ws://huly.local:8099/ws
+1 -1
View File
@@ -30,7 +30,7 @@
"BACKUP_URL": "http://huly.local:4039/api/backup",
"PULSE_URL": "ws://huly.local:8099/ws",
"HULYLAKE_URL": "http://huly.local:8096",
"EXCLUDED_APPLICATIONS_FOR_ANONYMOUS": "[\"chunter\", \"notification\"]",
"EXCLUDED_APPLICATIONS_FOR_ANONYMOUS": "[]",
"ANALYTICS_COLLECTOR_URL": "http://huly.local:4017",
"LINK_PREVIEW_URL": "http://huly.local:4041"
}
@@ -139,6 +139,7 @@ export interface Association extends Doc {
nameA: string
nameB: string
type: '1:1' | '1:N' | 'N:N'
automationOnly?: boolean
}
/**
+14
View File
@@ -947,6 +947,20 @@ export function createModel (builder: Builder): void {
card.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: card.app.Card,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: card.class.CardSpace,
enabled: false,
order: 20
},
card.ids.ModulePermissionGroupReadOnlyGuest
)
builder.mixin(card.class.Card, core.class.Class, view.mixin.ClassFilters, {
filters: ['space'],
ignoreKeys: ['parent']
+14
View File
@@ -83,6 +83,20 @@ export function createModel (builder: Builder): void {
chunter.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: chunter.app.Chunter,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: chunter.class.ChunterSpace,
enabled: true,
order: 15
},
chunter.ids.ModulePermissionGroupReadOnlyGuest
)
builder.createDoc(
workbench.class.Widget,
core.space.Model,
+2 -1
View File
@@ -95,7 +95,8 @@ export default mergeIds(chunterId, chunter, {
},
ids: {
ChunterNotificationGroup: '' as Ref<NotificationGroup>,
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
space: {
General: '' as Ref<Channel>,
+14
View File
@@ -1124,6 +1124,20 @@ export function createModel (builder: Builder): void {
},
documents.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: documents.app.Documents,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: documents.class.OrgSpace,
enabled: false,
order: 42
},
documents.ids.ModulePermissionGroupReadOnlyGuest
)
definePermissions(builder)
defineNotifications(builder)
defineSearch(builder)
+2 -1
View File
@@ -93,6 +93,7 @@ export default mergeIds(documentsId, documents, {
StateNotification: '' as Ref<NotificationType>
},
ids: {
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
}
})
+2
View File
@@ -150,6 +150,8 @@ export class TAssociation extends TDoc implements Association {
nameB!: string
type!: '1:1' | '1:N' | 'N:N'
automationOnly?: boolean
}
@Model(core.class.Relation, core.class.Doc, DOMAIN_RELATION)
+14
View File
@@ -553,6 +553,20 @@ export function createModel (builder: Builder): void {
},
document.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: document.app.Documents,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: document.class.Teamspace,
enabled: false,
order: 40
},
document.ids.ModulePermissionGroupReadOnlyGuest
)
definePermissions(builder)
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
+2 -1
View File
@@ -62,7 +62,8 @@ export default mergeIds(documentId, document, {
Other: '' as Ref<TagCategory>
},
ids: {
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
string: {
ConfigDescription: '' as IntlString,
+14
View File
@@ -855,5 +855,19 @@ export function createModel (builder: Builder): void {
},
drive.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: drive.app.Drive,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: drive.class.Drive,
enabled: false,
order: 60
},
drive.ids.ModulePermissionGroupReadOnlyGuest
)
definePermissions(builder)
}
+2 -1
View File
@@ -102,7 +102,8 @@ export default mergeIds(driveId, drive, {
RestoreFileVersion: '' as ViewAction
},
ids: {
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
string: {
Grid: '' as IntlString,
+14
View File
@@ -280,6 +280,20 @@ export function createModel (builder: Builder): void {
love.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: love.app.Love,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: love.class.Office,
enabled: false,
order: 50
},
love.ids.ModulePermissionGroupReadOnlyGuest
)
builder.createDoc(
workbench.class.Widget,
core.space.Model,
+2 -1
View File
@@ -51,7 +51,8 @@ export default mergeIds(loveId, love, {
Settings: '' as Ref<Doc>,
LoveNotificationGroup: '' as Ref<NotificationGroup>,
MeetingMinutesChatNotification: '' as Ref<NotificationType>,
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
function: {
MeetingMinutesTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
+1 -1
View File
@@ -318,7 +318,7 @@ export function createModel (builder: Builder): void {
{
name: 'guestPermissions',
label: setting.string.GuestPermissionsSettings,
icon: setting.icon.Members,
icon: setting.icon.GuestPermissions,
component: setting.component.GuestPermissionsSettings,
role: AccountRole.Owner,
order: 1050
+14
View File
@@ -216,6 +216,20 @@ export function createModel (builder: Builder): void {
testManagement.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: testManagement.app.TestManagement,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: testManagement.class.TestProject,
enabled: false,
order: 70
},
testManagement.ids.ModulePermissionGroupReadOnlyGuest
)
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectIcon, {
component: testManagement.component.TestCaseStatusPresenter
})
+2 -1
View File
@@ -50,6 +50,7 @@ export default mergeIds(testManagementId, testManganement, {
RunTestPlanButton: '' as AnyComponent
},
ids: {
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
}
})
+14
View File
@@ -682,6 +682,20 @@ export function createModel (builder: Builder): void {
tracker.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: tracker.app.Tracker,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: tracker.class.Project,
enabled: true,
order: 10
},
tracker.ids.ModulePermissionGroupReadOnlyGuest
)
builder.createDoc(
chunter.class.ChatMessageViewlet,
core.space.Model,
+1
View File
@@ -82,6 +82,7 @@ export default mergeIds(trackerId, tracker, {
BaseProjectType: '' as Ref<ProjectType>,
GuestIssueClassPermission: '' as Ref<Doc>,
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>,
IssueUpdatedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
IssueCreatedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
IssueRemovedActivityViewlet: '' as Ref<DocUpdateMessageViewlet>,
+14
View File
@@ -923,6 +923,20 @@ function defineSettings (builder: Builder): void {
},
training.ids.ModulePermissionGroup
)
builder.createDoc(
core.class.ModulePermissionGroup,
core.space.Model,
{
application: training.app.Training,
role: AccountRole.ReadOnlyGuest,
permissions: [],
spaceClass: core.class.TypedSpace,
enabled: false,
order: 25
},
training.ids.ModulePermissionGroupReadOnlyGuest
)
}
const columns = {
+2 -1
View File
@@ -35,7 +35,8 @@ export default mergeIds(trainingId, training, {
},
ids: {
GuestTrainingAttemptClassPermission: '' as Ref<Doc>,
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
// TODO: Move function resources declarations to plugins/*-resources
+53 -53
View File
@@ -113,10 +113,10 @@
--secondary-button-disabled: rgba(255, 255, 255, .12);
--secondary-button-disabled-color: rgba(255, 255, 255, .4);
--theme-button-default: rgba(255, 255, 255, .02);
--theme-button-hovered: rgba(255, 255, 255, .04);
--theme-button-pressed: rgba(255, 255, 255, .08);
--theme-button-focused: rgba(255, 255, 255, .04);
--theme-button-default: rgba(255, 255, 255, .03);
--theme-button-hovered: rgba(255, 255, 255, .07);
--theme-button-pressed: rgba(255, 255, 255, .11);
--theme-button-focused: rgba(255, 255, 255, .07);
--theme-button-focused-border: rgba(255, 255, 255, .09);
--theme-button-disabled: transparent;
--theme-button-border: rgba(255, 255, 255, .09);
@@ -125,8 +125,8 @@
--theme-breadcrumb-hovered: rgba(255, 255, 255, .08);
--theme-breadcrumb-pressed: rgba(255, 255, 255, .1);
--theme-button-icon-default: rgba(255, 255, 255, 0);
--theme-button-icon-hovered: rgba(255, 255, 255, .06);
--theme-button-icon-pressed: rgba(255, 255, 255, .1);
--theme-button-icon-hovered: rgba(255, 255, 255, .08);
--theme-button-icon-pressed: rgba(255, 255, 255, .12);
--theme-button-contrast-color: #000;
--theme-button-contrast-enabled: rgba(255, 255, 255, .8);
@@ -136,7 +136,7 @@
--theme-button-contrast-disabled-color: rgba(0, 0, 0, .5);
--theme-button-contrast-border: rgba(255, 255, 255, .2);
--theme-button-container-color: #25262A;
--theme-button-container-color: #22262B;
--theme-button-attention-gradient: linear-gradient(135deg, #7C3AED, #A855F7);
--theme-button-attention-border: #A855F7;
@@ -151,13 +151,13 @@
--theme-refinput-border: rgba(255, 255, 255, .1);
// Be aware to update defineAlpha() function in colors.ts
--theme-bg-color: #1A1A28;
--theme-bg-color: #161719;
--theme-bg-accent-color: rgba(0, 0, 0, .08);
--theme-bg-dark-color: rgba(0, 0, 0, .2);
--theme-back-color: #0f0f18;
--theme-back-color: #0E0F10;
--theme-overlay-color: rgba(0, 0, 0, .3);
--theme-statusbar-color: #1A1928;
--theme-navpanel-color: #14141F;
--theme-statusbar-color: #0E0F10;
--theme-navpanel-color: #0E0F10;
--theme-navpanel-hovered: rgba(255, 255, 255, .04);
--theme-navpanel-selected: rgba(255, 255, 255, .08);
--theme-navpanel-divider: rgba(255, 255, 255, .1);
@@ -165,9 +165,9 @@
--theme-navpanel-icons-color: #7F7F7F;
--theme-navpanel-icons-divider: rgba(255, 255, 255, .11);
--theme-navpanel-shadow-mobile: drop-shadow(0 0 3px rgba(0, 0, 0, .5));
--theme-comp-header-color: #1F1F2C;
--theme-comp-header-color: #1E2024;
--theme-divider-color: rgba(255, 255, 255, .06);
--theme-bg-divider-color: #282834;
--theme-bg-divider-color: #22242A;
--theme-mention-bg-color: rgba(55, 122, 230, 0.1);
--theme-mention-focused-bg-color: rgba(55, 122, 230, 0.2);
--theme-broken-mention-color: rgba(255, 255, 255, .4);
@@ -184,22 +184,22 @@
--theme-list-border-color: rgba(255, 255, 255, .05);
--theme-list-header-color: #C88C65;
--theme-list-subheader-color: #262634;
--theme-list-row-color: #21212F;
--theme-list-subheader-color: #22242A;
--theme-list-row-color: #1D1F23;
--theme-list-divider-color: rgba(255, 255, 255, .09);
--theme-list-subheader-divider: rgba(255, 255, 255, .06);
--theme-list-button-color: #262633;
--theme-list-button-hover: #2F2F3A;
--theme-link-button-color: #262634;
--theme-link-button-hover: #2F2F3B;
--theme-list-button-color: #22242A;
--theme-list-button-hover: #2A2C32;
--theme-link-button-color: #22242A;
--theme-link-button-hover: #2A2C33;
--theme-table-border-color: rgba(255, 255, 255, .1);
--theme-table-header-color: #1C1C29;
--theme-table-row-color: #21212F;
--theme-table-header-color: #1A1C20;
--theme-table-row-color: #1D1F23;
--theme-kanban-card-bg-color: rgba(222, 222, 240, .04);
--theme-kanban-card-border: transparent;
--theme-kanban-card-bg-color: rgba(222, 222, 240, .055);
--theme-kanban-card-border: rgba(255, 255, 255, .06);
--theme-kanban-card-footer: rgba(217, 217, 217, .07);
--theme-editbox-focus-color: rgba(255, 255, 255, .04);
@@ -212,16 +212,16 @@
--theme-checkbox-border: rgba(0, 0, 0, .12);
--theme-checkbox-disabled: #999;
--theme-progress-color: #FFFFFF;
--theme-popup-color: #2A2939;
--theme-popup-trans-color: #2A2939ee;
--theme-popup-trans-gradient: linear-gradient(to bottom, #2A2939ff, #2A293900);
--theme-popup-hover: #333240;
--theme-popup-divider: rgba(255, 255, 255, .09);
--theme-popup-header: #3A3A47;
--theme-popup-color: #1F2328;
--theme-popup-trans-color: #1F2328ee;
--theme-popup-trans-gradient: linear-gradient(to bottom, #1F2328ff, #1F232800);
--theme-popup-hover: #2C3139;
--theme-popup-divider: rgba(255, 255, 255, .08);
--theme-popup-header: #323842;
--theme-popup-shadow: 0 0 .5rem rgba(0, 0, 0, .2);
--theme-popup-checkicon: #FFFFFF99;
--theme-popup-deactivated: #1A1A28;
--theme-panel-color: #1A1A28;
--theme-popup-deactivated: #161719;
--theme-panel-color: #161719;
--theme-calendar-today-color: #fff;
--theme-calendar-holiday-color: #eb5757;
--theme-calendar-weekend-color: rgba(242, 153, 74, 1);
@@ -245,17 +245,17 @@
--theme-diffview-delete-color: rgb(248, 81, 73);
--theme-tooltip-color: rgba(255, 255, 255, .8);
--theme-tooltip-bg: #353347;
--theme-tooltip-bg: #2C3139;
--theme-tooltip-key-bg: rgba(255, 255, 255, .08);
--theme-inbox-notify: #F47758;
--theme-inbox-people-notify: #2B5190;
--theme-inbox-activity-bgcolor: #1A1A28;
--theme-inbox-activity-bgcolor: #0E0F10;
--theme-inbox-activitymsg-bgcolor: rgba(255, 255, 255, .03);
--theme-inbox-activitymsg-divider: rgba(255, 255, 255, .1);
--theme-inbox-activitymsg-border: rgba(255, 255, 255, .03);
--theme-inbox-counter-bgcolor: rgba(255, 255, 255, .06);
--theme-inbox-people-counter-bgcolor: rgba(43, 81, 144, .1);
--theme-inbox-people-counter-bgcolor: rgba(32, 93, 194, .14);
--theme-toggle-sw-color: #fff;
--theme-toggle-on-sw-color: #fff;
@@ -309,13 +309,13 @@
--theme-text-editor-palette-bg-pink: rgba(78, 44, 60, 1);
--theme-text-editor-palette-bg-red:rgba(82, 46, 42, 1);
--accent-bg-color: #27282b;
--accent-bg-color: #22262B;
--accent-shadow: rgb(0 0 0 / 10%) 0px 2px 4px;
--highlight-hover: #282834;
--highlight-select: #252b3a;
--highlight-select-border: #44506b;
--highlight-select-hover: #2c3346;
--highlight-hover: #2A2E34;
--highlight-select: #2C3139;
--highlight-select-border: #3D4450;
--highlight-select-hover: #323842;
--scrollbar-bar-color: #35354a;
--scrollbar-bar-hover: #8a8aa5;
@@ -330,13 +330,13 @@
--divider-color: #303236;
--divider-trans-color: rgba(255, 255, 255, .12);
--menu-bg-select: #2d2f36;
--menu-bg-select-trans: #2d2f3665;
--menu-bg-select: #2C3139;
--menu-bg-select-trans: #2C313965;
--menu-icon-hover: #f3f3f8;
--header-bg-color: linear-gradient(0deg, var(--accent-bg-color), #2d2e31);
--popup-bg-color: linear-gradient(136.61deg, var(--accent-bg-color) 13.72%, #2d2e31 74.3%);
--popup-bg-hover: #37373c;
--popup-divider: #313236;
--header-bg-color: linear-gradient(0deg, var(--accent-bg-color), #2A2E34);
--popup-bg-color: linear-gradient(136.61deg, var(--accent-bg-color) 13.72%, #2C3139 74.3%);
--popup-bg-hover: #323842;
--popup-divider: #2E333C;
--popup-shadow: rgb(0 0 0 / 50%) 0px 4px 24px;
--popup-panel-shadow: rgb(0 0 0 / 55%) 0px 7px 24px;
--popup-aside-shadow: rgb(0 0 0 / 25%) 0px 8px 16px;
@@ -381,28 +381,28 @@
--theme-clockface-arrows-holder: radial-gradient(at top center, #2F2F3A, #555555);
--theme-clockface-arrows-shadow: 0 0 1px white;
--theme-link-preview-bg-color: #2B2D41;
--theme-link-preview-bg-color: #252A32;
--theme-link-preview-description-color: #C1C9D6;
--theme-link-preview-text-color:#FFFFFF;
--theme-dialog-border-color: rgba(255, 255, 255, 0.1);
--theme-dialog-background-color: #2a2938;
--theme-dialog-background-color: #1F2328;
--theme-dialog-back-color: #848484;
--theme-icon-stroke: #e8e9e9;
--theme-state-ghost-color: rgba(123, 123, 123, 0.6);
--theme-state-ghost-background-color: rgba(123, 123, 123, 0.1);
--theme-state-ghost-border-color: transparent;
--theme-state-primary-color: #3070dc;
--theme-state-primary-background-color: rgba(48, 112, 220, 0.1);
--theme-state-primary-border-color: rgba(48, 112, 220, 0.15);
--theme-state-primary-color: #377AE6;
--theme-state-primary-background-color: rgba(55, 122, 230, 0.14);
--theme-state-primary-border-color: rgba(55, 122, 230, 0.26);
--theme-state-regular-color: #7b7b7b;
--theme-state-regular-background-color: rgba(123, 123, 123, 0.1);
--theme-state-regular-border-color: rgba(123, 123, 123, 0.15);
--theme-wizard-not-visited-color: #34343c;
--calendar-event-back-color: #2B2D41;
--calendar-event-border-color: rgba(43, 81, 144, 0.2);
--calendar-event-handle-color: #2b5190;
--calendar-event-back-color: #252A32;
--calendar-event-border-color: rgba(55, 122, 230, 0.28);
--calendar-event-handle-color: #205DC2;
--theme-label-green-color: rgba(34, 197, 94, .8);
--theme-label-green-bg-color: rgba(34, 197, 94, .08);
+9 -9
View File
@@ -49,17 +49,17 @@
--global-ui-active-BackgroundColor: #A5BDFF26;
--global-ui-highlight-BackgroundColor: #A5BDFF0D;
--global-ui-hover-highlight-BackgroundColor: #A5BDFF26;
--global-surface-01-BackgroundColor: #131925;
--global-surface-01-BackgroundColor: #0E0F10;
--global-surface-01-BorderColor: #1F2737;
--global-surface-01-hover-BackgroundColor: #19202E;
--global-surface-02-BackgroundColor: #19202E;
--global-surface-01-hover-BackgroundColor: #121314;
--global-surface-02-BackgroundColor: #161719;
--global-surface-02-BorderColor: #262F40;
--global-surface-03-hover-BackgroundColor: #19202E;
--global-surface-03-hover-BackgroundColor: #1A1C1F;
--global-subtle-ui-BorderColor: #A5BDFF0D;
--global-subtle-BackgroundColor: #072790;
--global-popover-BackgroundColor: #262F40;
--global-popover-hover-BackgroundColor: #1F2737;
--global-popover-BorderColor: #A5BDFF1A;
--global-popover-BackgroundColor: #1F2328;
--global-popover-hover-BackgroundColor: #2C3139;
--global-popover-BorderColor: #FFFFFF14;
--global-primary-LinkColor: #4D7FF5;
--global-primary-IconColor: #ffffff;
--global-primary-TextColor: #FFFFFF;
@@ -73,7 +73,7 @@
--global-popover-ShadowColor: #0E131E59;
--global-modal-ShadowColor: #0E131E73;
--global-accent-SkyText: #B9D1F5;
--global-accent-BackgroundColor: #204DC8;
--global-accent-BackgroundColor: #205DC2;
--global-on-nuance-TextColor: #041d7d;
--global-no-priority-PriorityColor: #8E99AF;
@@ -91,7 +91,7 @@
--icon-disabled-IconColor: #394358;
--border-talk-indication-secondary: #25262A;
--border-talk-indication-secondary: #22262B;
/** Buttons **/
--button-subtle-LabelColor: #fff;
+2 -2
View File
@@ -212,7 +212,7 @@ export const avatarDarkColors = Object.freeze<ColorDefinition[]>([
])
export function defaultBackground (dark: boolean): string {
return dark ? 'linear-gradient(90deg, #262634, #1A1A28)' : '#FFFFFF'
return dark ? 'linear-gradient(90deg, #22242A, #161719)' : '#FFFFFF'
}
export const FeijoaColor = '#A5D179'
@@ -418,7 +418,7 @@ export function defineAlpha (dark: boolean, color: string, percent = 100): strin
const p1 = percent / 100
const bg = dark ? hexToRgb('#1A1A28') : hexToRgb('#F1F1F4')
const bg = dark ? hexToRgb('#161719') : hexToRgb('#F1F1F4')
return rgbToHex({
r: r * p1 + bg.r * (1 - p1),
@@ -80,10 +80,11 @@
background-color: var(--global-accent-BackgroundColor);
.icon {
color: var(--global-on-nuance-TextColor);
color: var(--global-on-accent-TextColor);
}
span {
color: var(--global-on-nuance-TextColor);
color: var(--global-on-accent-TextColor);
font-weight: 500;
}
}
&:checked + .switcher-element.subtle {
+2 -1
View File
@@ -226,7 +226,8 @@ const cardPlugin = plugin(cardId, {
ids: {
CardWidget: '' as Ref<Doc>,
GuestCardClassPermission: '' as Ref<Doc>,
ModulePermissionGroup: '' as Ref<Doc>
ModulePermissionGroup: '' as Ref<Doc>,
ModulePermissionGroupReadOnlyGuest: '' as Ref<Doc>
},
component: {
LabelsPresenter: '' as AnyComponent,
+5 -5
View File
@@ -863,9 +863,9 @@ export async function subProcessMatchCheck (
params: Record<string, any>,
context: Record<string, any>
): Promise<boolean> {
const { process: _process, ...otherCritera } = params
const { process: _process, currentState } = params
if (_process === undefined) return false
if (Object.keys(otherCritera).length === 0) return true
if (currentState === undefined) return true
const subProcesses = await client.findAll(process.class.Execution, {
parentId: execution._id,
@@ -874,18 +874,18 @@ export async function subProcessMatchCheck (
if (subProcesses.length === 0) return false
const [predicate, value] = Object.entries(otherCritera)[0]
const [predicate, value] = Object.entries(currentState)[0]
const res = matchQuery(
subProcesses,
{ currentState: { $in: value } },
{ currentState: { $in: value as any } },
process.class.Execution,
client.getHierarchy(),
true
)
if (predicate === '$all') {
return res.length === subProcesses.length
} else if (predicate === '$any') {
} else if (predicate === '$in') {
return res.length > 0
} else if (predicate === '$nin') {
return res.length === 0
+11
View File
@@ -15,6 +15,17 @@
<path d="M9.6,16.2c-2.1,0-3.9-1.9-4-4.3c-0.1-1.2,0.3-2.3,1-3.1c0.8-0.8,1.8-1.2,3-1.2c1.2,0,2.2,0.4,3,1.3c0.8,0.8,1.1,1.9,1.1,3.1C13.5,14.3,11.7,16.2,9.6,16.2z M9.6,9.5C9,9.5,8.4,9.7,8,10.1c-0.4,0.4-0.6,1-0.5,1.7c0.1,1.4,1.1,2.5,2.2,2.5s2.1-1.2,2.2-2.5c0-0.7-0.2-1.3-0.6-1.7C10.9,9.7,10.3,9.5,9.6,9.5z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M2,22.5c0.9-3.5,4.3-5.2,7.6-5.2c1.3,0,2.6,0.2,3.8,0.8c0.5,0.2,0.7,0.8,0.5,1.2c-0.2,0.5-0.8,0.7-1.2,0.5c-0.9-0.4-1.9-0.6-3.1-0.6c-2.6,0-4.9,1.2-5.7,3.4H10c0.5,0,0.9,0.4,0.9,0.9s-0.4,0.9-0.9,0.9H3.5C2.4,24.4,1.8,23.3,2,22.5L2,22.5z" />
</symbol>
<symbol id="guestPermissions" viewBox="2 0 30 32">
<!-- accountSettings paths, scaled from (8, 8) -->
<g transform="translate(11, 15.5) scale(1.7) translate(-8, -8)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8ZM8 9C10.2091 9 12 7.20914 12 5C12 2.79086 10.2091 1 8 1C5.79086 1 4 2.79086 4 5C4 7.20914 5.79086 9 8 9Z" />
<path d="M6.5 10C4.567 10 3 11.567 3 13.5V14.5C3 14.7761 3.22386 15 3.5 15C3.77614 15 4 14.7761 4 14.5V13.5C4 12.1193 5.11929 11 6.5 11H9.5C10.8807 11 12 12.1193 12 13.5V14.5C12 14.7761 12.2239 15 12.5 15C12.7761 15 13 14.7761 13 14.5V13.5C13 11.567 11.433 10 9.5 10H6.5Z" />
</g>
<!-- Small lock badge, lower-right near shoulders -->
<g transform="translate(19, 13)">
<path d="M12,7.1h-0.7V5.4c0-1.8-1.5-3.3-3.3-3.3c-1.8,0-3.3,1.5-3.3,3.3v1.7H4c-0.8,0-1.5,0.7-1.5,1.5v4.1c0,0.8,0.7,1.5,1.5,1.5h8 c0.8,0,1.5-0.7,1.5-1.5V8.6C13.5,7.8,12.8,7.1,12,7.1z M5.7,5.4c0-1.2,1-2.3,2.3-2.3s2.3,1,2.3,2.3v1.7H5.7V5.4z M12.5,12.7 c0,0.3-0.2,0.5-0.5,0.5H4c-0.3,0-0.5-0.2-0.5-0.5V8.6c0-0.3,0.2-0.5,0.5-0.5h8c0.3,0,0.5,0.2,0.5,0.5V12.7z" />
</g>
</symbol>
<symbol id="password" viewBox="0 0 16 16">
<path d="M11 6C11.5523 6 12 5.55228 12 5C12 4.44772 11.5523 4 11 4C10.4477 4 10 4.44772 10 5C10 5.55228 10.4477 6 11 6Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 1C8.01472 1 6 3.01472 6 5.5C6 5.89536 6.05111 6.27942 6.14726 6.64563L1.29289 11.5C1.10536 11.6875 1 11.9419 1 12.2071V14.5C1 14.7761 1.22386 15 1.5 15H3.79289C4.05811 15 4.31246 14.8946 4.5 14.7071L9.35437 9.85274C9.72058 9.94889 10.1046 10 10.5 10C12.9853 10 15 7.98528 15 5.5C15 3.01472 12.9853 1 10.5 1ZM7 5.5C7 3.567 8.567 2 10.5 2C12.433 2 14 3.567 14 5.5C14 7.433 12.433 9 10.5 9C10.1048 9 9.72588 8.93469 9.37284 8.81468C9.19252 8.75339 8.99304 8.79986 8.85837 8.93453L8 9.79289L7.85355 9.64645C7.65829 9.45118 7.34171 9.45118 7.14645 9.64645C6.95118 9.84171 6.95118 10.1583 7.14645 10.3536L7.29289 10.5L6.5 11.2929L5.85355 10.6464C5.65829 10.4512 5.34171 10.4512 5.14645 10.6464C4.95118 10.8417 4.95118 11.1583 5.14645 11.3536L5.79289 12L5 12.7929L4.35355 12.1464C4.15829 11.9512 3.84171 11.9512 3.64645 12.1464C3.45118 12.3417 3.45118 12.6583 3.64645 12.8536L4.29289 13.5L3.79289 14H2V12.2071L7.06547 7.14163C7.20014 7.00696 7.24661 6.80748 7.18532 6.62716C7.06531 6.27412 7 5.8952 7 5.5Z" />

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

+6 -3
View File
@@ -212,9 +212,12 @@
"OfficeDefaultSettings": "Výchozí nastavení pro jednací místnosti",
"DefaultStartWithTranscription": "Povolit přepis v nových kancelářích",
"DefaultStartWithRecording": "Povolit záznam v nových kancelářích",
"GuestPermissionsSettings": "Oprávnění hostů",
"GuestPermissionsModulePermissions": "Oprávnění modulů",
"GuestPermissionsModulePermissionsHint": "Vyberte, které moduly mohou hosté používat, a níže upravte oprávnění pro jednotlivé aplikace.",
"GuestPermissionsSettings": "Hosté",
"GuestPermissionsApplicationPermissions": "Oprávnění aplikací",
"GuestPermissionsApplicationPermissionsHint": "Vyberte, které aplikace mohou hosté používat, a níže upravte oprávnění pro jednotlivé aplikace.",
"GuestPermissionsTabGuest": "Host",
"GuestPermissionsTabAnonymousGuest": "Anonymní host",
"GuestPermissionsAnonymousApplicationHint": "Přístup k aplikacím pro anonymní (pouze pro čtení) hosty. Které aplikace uvidí, může záviset i na konfiguraci nasazení.",
"ImportDocumentPermission": "Importovat dokumenty",
"ImportDocumentDescription": "Umožňuje uživatelům importovat dokumenty do pracovního prostoru",
"SelectUsers": "Vybrat uživatele",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "Standardeinstellungen für Besprechungsräume",
"DefaultStartWithTranscription": "Transkription in neuen Büroräumen aktivieren",
"DefaultStartWithRecording": "Aufnahme in neuen Büroräumen aktivieren",
"GuestPermissionsSettings": "Gastberechtigungen",
"GuestPermissionsModulePermissions": "Modulberechtigungen",
"GuestPermissionsModulePermissionsHint": "Wählen Sie, welche Module Gäste nutzen dürfen, und passen Sie unten die Berechtigungen für jede App an.",
"GuestPermissionsSettings": "Gäste",
"GuestPermissionsApplicationPermissions": "Anwendungsberechtigungen",
"GuestPermissionsApplicationPermissionsHint": "Wählen Sie, welche Anwendungen Gäste nutzen dürfen, und passen Sie unten die Berechtigungen für jede Anwendung an.",
"GuestPermissionsTabGuest": "Gast",
"GuestPermissionsTabAnonymousGuest": "Anonymer Gast",
"GuestPermissionsAnonymousApplicationHint": "Anwendungszugriff für anonyme (nur lesende) Gäste. Welche Anwendungen sie sehen, kann auch von der Bereitstellungskonfiguration abhängen.",
"ImportDocumentPermission": "Dokumente importieren",
"ImportDocumentDescription": "Gewährt Benutzern die Möglichkeit, Dokumente in den Arbeitsbereich zu importieren",
"SelectUsers": "Benutzer auswählen",
+6 -3
View File
@@ -215,9 +215,12 @@
"OfficeDefaultSettings": "Default settings for meeting rooms",
"DefaultStartWithTranscription": "Enable transcription in new office rooms",
"DefaultStartWithRecording": "Enable recording in new office rooms",
"GuestPermissionsSettings": "Guest permissions",
"GuestPermissionsModulePermissions": "Module permissions",
"GuestPermissionsModulePermissionsHint": "Choose which modules guests can use, then adjust permissions for each app below.",
"GuestPermissionsSettings": "Guests",
"GuestPermissionsApplicationPermissions": "Application permissions",
"GuestPermissionsApplicationPermissionsHint": "Choose which applications guests can use, then adjust permissions for each application below.",
"GuestPermissionsTabGuest": "Guest",
"GuestPermissionsTabAnonymousGuest": "Anonymous guest",
"GuestPermissionsAnonymousApplicationHint": "Application access for anonymous (read-only) guests. Which applications they see can also depend on deployment configuration.",
"ImportDocumentPermission": "Import documents",
"ImportDocumentDescription": "Grants users ability to import documents into the workspace",
"SelectUsers": "Select users",
+6 -3
View File
@@ -205,9 +205,12 @@
"OfficeDefaultSettings": "Configuración predeterminada para salas de reuniones",
"DefaultStartWithTranscription": "Habilitar transcripción en nuevas oficinas",
"DefaultStartWithRecording": "Habilitar grabación en nuevas oficinas",
"GuestPermissionsSettings": "Permisos de invitados",
"GuestPermissionsModulePermissions": "Permisos de módulos",
"GuestPermissionsModulePermissionsHint": "Elija qué módulos pueden usar los invitados y luego ajuste los permisos de cada aplicación a continuación.",
"GuestPermissionsSettings": "Invitados",
"GuestPermissionsApplicationPermissions": "Permisos de aplicaciones",
"GuestPermissionsApplicationPermissionsHint": "Elija qué aplicaciones pueden usar los invitados y luego ajuste los permisos de cada aplicación a continuación.",
"GuestPermissionsTabGuest": "Invitado",
"GuestPermissionsTabAnonymousGuest": "Invitado anónimo",
"GuestPermissionsAnonymousApplicationHint": "Acceso a aplicaciones para invitados anónimos (solo lectura). Las aplicaciones que ven también pueden depender de la configuración de despliegue.",
"ImportDocumentPermission": "Importar documentos",
"ImportDocumentDescription": "Otorga a los usuarios la capacidad de importar documentos al espacio de trabajo",
"SelectUsers": "Seleccionar usuarios",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "Paramètres par défaut pour les salles de réunion",
"DefaultStartWithTranscription": "Activer la transcription dans les nouveaux bureaux",
"DefaultStartWithRecording": "Activer l'enregistrement dans les nouveaux bureaux",
"GuestPermissionsSettings": "Permissions des invités",
"GuestPermissionsModulePermissions": "Permissions des modules",
"GuestPermissionsModulePermissionsHint": "Choisissez les modules accessibles aux invités, puis ajustez les permissions pour chaque application ci-dessous.",
"GuestPermissionsSettings": "Invités",
"GuestPermissionsApplicationPermissions": "Permissions des applications",
"GuestPermissionsApplicationPermissionsHint": "Choisissez les applications accessibles aux invités, puis ajustez les permissions pour chaque application ci-dessous.",
"GuestPermissionsTabGuest": "Invité",
"GuestPermissionsTabAnonymousGuest": "Invité anonyme",
"GuestPermissionsAnonymousApplicationHint": "Accès aux applications pour les invités anonymes (lecture seule). Les applications visibles peuvent aussi dépendre de la configuration de déploiement.",
"ImportDocumentPermission": "Importer des documents",
"ImportDocumentDescription": "Accorde aux utilisateurs la possibilité d'importer des documents dans l'espace de travail",
"SelectUsers": "Sélectionner des utilisateurs",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "Impostazioni predefinite per le sale riunioni",
"DefaultStartWithTranscription": "Abilita trascrizione nelle nuove stanze dell'ufficio",
"DefaultStartWithRecording": "Abilita registrazione nelle nuove stanze dell'ufficio",
"GuestPermissionsSettings": "Permessi ospite",
"GuestPermissionsModulePermissions": "Permessi dei moduli",
"GuestPermissionsModulePermissionsHint": "Scegli quali moduli possono usare gli ospiti, poi regola i permessi per ogni app qui sotto.",
"GuestPermissionsSettings": "Ospiti",
"GuestPermissionsApplicationPermissions": "Permessi delle applicazioni",
"GuestPermissionsApplicationPermissionsHint": "Scegli quali applicazioni possono usare gli ospiti, poi regola i permessi per ogni applicazione qui sotto.",
"GuestPermissionsTabGuest": "Ospite",
"GuestPermissionsTabAnonymousGuest": "Ospite anonimo",
"GuestPermissionsAnonymousApplicationHint": "Accesso alle applicazioni per ospiti anonimi (sola lettura). Le applicazioni visibili possono dipendere anche dalla configurazione di distribuzione.",
"ImportDocumentPermission": "Importa documenti",
"ImportDocumentDescription": "Concede agli utenti la possibilità di importare documenti nell'area di lavoro",
"SelectUsers": "Seleziona utenti",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "会議室のデフォルト設定",
"DefaultStartWithTranscription": "新しいオフィスルームで文字起こしを有効にする",
"DefaultStartWithRecording": "新しいオフィスルームで録画を有効にする",
"GuestPermissionsSettings": "ゲストの権限",
"GuestPermissionsModulePermissions": "モジュールの権限",
"GuestPermissionsModulePermissionsHint": "ゲストが利用できるモジュールを選び、その下で各アプリの権限を調整します。",
"GuestPermissionsSettings": "ゲスト",
"GuestPermissionsApplicationPermissions": "アプリケーションの権限",
"GuestPermissionsApplicationPermissionsHint": "ゲストが利用できるアプリケーションを選び、その下で各アプリケーションの権限を調整します。",
"GuestPermissionsTabGuest": "ゲスト",
"GuestPermissionsTabAnonymousGuest": "匿名ゲスト",
"GuestPermissionsAnonymousApplicationHint": "匿名(読み取り専用)ゲストのアプリケーションアクセス。表示されるアプリケーションはデプロイ設定にも依存する場合があります。",
"ImportDocumentPermission": "ドキュメントをインポート",
"ImportDocumentDescription": "ユーザーにワークスペースにドキュメントをインポートする機能を付与します",
"SelectUsers": "ユーザーを選択",
+6 -3
View File
@@ -205,9 +205,12 @@
"OfficeDefaultSettings": "Configurações padrão para salas de reunião",
"DefaultStartWithTranscription": "Habilitar transcrição em novos escritórios",
"DefaultStartWithRecording": "Habilitar gravação em novos escritórios",
"GuestPermissionsSettings": "Permissões de convidados",
"GuestPermissionsModulePermissions": "Permissões de módulos",
"GuestPermissionsModulePermissionsHint": "Escolha quais módulos os convidados podem usar e, em seguida, ajuste as permissões de cada aplicativo abaixo.",
"GuestPermissionsSettings": "Convidados",
"GuestPermissionsApplicationPermissions": "Permissões de aplicativos",
"GuestPermissionsApplicationPermissionsHint": "Escolha quais aplicativos os convidados podem usar e, em seguida, ajuste as permissões de cada aplicativo abaixo.",
"GuestPermissionsTabGuest": "Convidado",
"GuestPermissionsTabAnonymousGuest": "Convidado anônimo",
"GuestPermissionsAnonymousApplicationHint": "Acesso a aplicativos para convidados anônimos (somente leitura). Os aplicativos visíveis também podem depender da configuração de implantação.",
"ImportDocumentPermission": "Importar documentos",
"ImportDocumentDescription": "Concede aos usuários a capacidade de importar documentos para o espaço de trabalho",
"SelectUsers": "Selecionar usuários",
+6 -3
View File
@@ -205,9 +205,12 @@
"OfficeDefaultSettings": "Configurações padrão para salas de reunião",
"DefaultStartWithTranscription": "Habilitar transcrição em novos escritórios",
"DefaultStartWithRecording": "Habilitar gravação em novos escritórios",
"GuestPermissionsSettings": "Permissões de convidados",
"GuestPermissionsModulePermissions": "Permissões de módulos",
"GuestPermissionsModulePermissionsHint": "Escolha quais módulos os convidados podem usar e, em seguida, ajuste as permissões de cada aplicação abaixo.",
"GuestPermissionsSettings": "Convidados",
"GuestPermissionsApplicationPermissions": "Permissões de aplicações",
"GuestPermissionsApplicationPermissionsHint": "Escolha quais aplicações os convidados podem usar e, em seguida, ajuste as permissões de cada aplicação abaixo.",
"GuestPermissionsTabGuest": "Convidado",
"GuestPermissionsTabAnonymousGuest": "Convidado anónimo",
"GuestPermissionsAnonymousApplicationHint": "Acesso a aplicações para convidados anónimos (só leitura). As aplicações visíveis também podem depender da configuração de implementação.",
"ImportDocumentPermission": "Importar documentos",
"ImportDocumentDescription": "Concede aos usuários a capacidade de importar documentos para o espaço de trabalho",
"SelectUsers": "Selecionar usuários",
+6 -3
View File
@@ -215,9 +215,12 @@
"OfficeDefaultSettings": "Настройки по умолчанию для переговорных",
"DefaultStartWithTranscription": "Включить транскрипцию в новых комнатах",
"DefaultStartWithRecording": "Включить запись в новых комнатах",
"GuestPermissionsSettings": "Права гостей",
"GuestPermissionsModulePermissions": "Права модулей",
"GuestPermissionsModulePermissionsHint": "Выберите, какие модули доступны гостям, затем настройте права для каждого приложения ниже.",
"GuestPermissionsSettings": "Гости",
"GuestPermissionsApplicationPermissions": "Права приложений",
"GuestPermissionsApplicationPermissionsHint": "Выберите, какие приложения доступны гостям, затем настройте права для каждого приложения ниже.",
"GuestPermissionsTabGuest": "Гость",
"GuestPermissionsTabAnonymousGuest": "Анонимный гость",
"GuestPermissionsAnonymousApplicationHint": "Доступ к приложениям для анонимных гостей (только чтение). Видимые приложения также могут зависеть от конфигурации развёртывания.",
"ImportDocumentPermission": "Импорт документов",
"ImportDocumentDescription": "Предоставляет пользователям возможность импортировать документы в рабочее пространство",
"SelectUsers": "Выбрать пользователей",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "Toplantı odaları için varsayılan ayarlar",
"DefaultStartWithTranscription": "Yeni ofis odalarında transkripsiyonu etkinleştir",
"DefaultStartWithRecording": "Yeni ofis odalarında kaydı etkinleştir",
"GuestPermissionsSettings": "Misafir izinleri",
"GuestPermissionsModulePermissions": "Modül izinleri",
"GuestPermissionsModulePermissionsHint": "Misafirlerin hangi modülleri kullanabileceğini seçin, ardından her uygulama için izinleri aşağıdan ayarlayın.",
"GuestPermissionsSettings": "Misafirler",
"GuestPermissionsApplicationPermissions": "Uygulama izinleri",
"GuestPermissionsApplicationPermissionsHint": "Misafirlerin hangi uygulamaları kullanabileceğini seçin, ardından her uygulama için izinleri aşağıdan ayarlayın.",
"GuestPermissionsTabGuest": "Misafir",
"GuestPermissionsTabAnonymousGuest": "Anonim misafir",
"GuestPermissionsAnonymousApplicationHint": "Anonim (salt okunur) misafirler için uygulama erişimi. Gördükleri uygulamalar da dağıtım yapılandırmasına bağlı olabilir.",
"ImportDocumentPermission": "Belgeleri içe aktar",
"ImportDocumentDescription": "Kullanıcılara çalışma alanına belge içe aktarma yeteneği verir",
"SelectUsers": "Kullanıcıları seç",
+6 -3
View File
@@ -214,9 +214,12 @@
"OfficeDefaultSettings": "会议室的默认设置",
"DefaultStartWithTranscription": "在新办公室启用转录",
"DefaultStartWithRecording": "在新办公室启用录制",
"GuestPermissionsSettings": "访客权限",
"GuestPermissionsModulePermissions": "模块权限",
"GuestPermissionsModulePermissionsHint": "选择访客可以使用哪些模块,然后在下方调整每个应用的权限。",
"GuestPermissionsSettings": "访客",
"GuestPermissionsApplicationPermissions": "应用权限",
"GuestPermissionsApplicationPermissionsHint": "选择访客可以使用哪些应用,然后在下方调整每个应用的权限。",
"GuestPermissionsTabGuest": "访客",
"GuestPermissionsTabAnonymousGuest": "匿名访客",
"GuestPermissionsAnonymousApplicationHint": "匿名(只读)访客的应用访问。他们可见的应用也可能取决于部署配置。",
"ImportDocumentPermission": "导入文档",
"ImportDocumentDescription": "授予用户将文档导入工作区的权限",
"SelectUsers": "选择用户",
+1
View File
@@ -21,6 +21,7 @@ loadMetadata(setting.icon, {
AccountSettings: `${icons}#accountSettings`,
Owners: `${icons}#owners`,
Members: `${icons}#members`,
GuestPermissions: `${icons}#guestPermissions`,
Password: `${icons}#password`,
Setting: `${icons}#settings`,
Integrations: `${icons}#integration`,
@@ -24,7 +24,8 @@
DropdownLabelsIntl,
EditBox,
Label,
NestedDropdown
NestedDropdown,
Toggle
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import card from '@hcengineering/card'
@@ -96,6 +97,7 @@
let classBRef: Ref<Class<Doc>> | undefined = undefined
let nameA = association.nameA
let nameB = association.nameB
let automationOnly = association.automationOnly ?? false
$: classA = isEmptyClass(classARef) ? undefined : hierarchy.getClass(classARef as Ref<Class<Doc>>)
$: classB = isEmptyClass(classBRef) ? undefined : hierarchy.getClass(classBRef as Ref<Class<Doc>>)
@@ -113,6 +115,7 @@
classARef = !isEmptyClass(association.classA) ? association.classA : undefined
nameA = association.nameA
nameB = association.nameB
automationOnly = association.automationOnly ?? false
}
function isAssociation (data: Data<Association> | Association): data is Association {
@@ -128,7 +131,8 @@
if (association !== undefined && isAssociation(association)) {
await client.diffUpdate(association, {
nameA,
nameB
nameB,
automationOnly
})
} else {
await client.createDoc(core.class.Association, core.space.Model, {
@@ -136,7 +140,8 @@
classB: classBRef,
type: mode,
nameA,
nameB
nameB,
automationOnly
})
dispatch('create')
dispatch('close')
@@ -224,6 +229,13 @@
{/if}
</div>
</div>
<div class="flex-col p-4 flex-gap-2">
<span class="label">
<Label label={view.string.AutomationOnly} />
</span>
<Toggle bind:on={automationOnly} />
</div>
</div>
<div class="flex-col-reverse">
@@ -17,7 +17,7 @@
import core, { Class, Doc, Ref } from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import presentation, { Card, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Label, NestedDropdown } from '@hcengineering/ui'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Label, NestedDropdown, Toggle } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import setting from '../plugin'
@@ -96,6 +96,7 @@
let classBRef: Ref<Class<Doc>> | undefined = undefined
let nameA: string = ''
let nameB: string = ''
let automationOnly = false
async function save (): Promise<void> {
if (classBRef === undefined || classARef === undefined) {
@@ -106,7 +107,8 @@
classB: classBRef,
type: mode,
nameA,
nameB
nameB,
automationOnly
})
dispatch('close', _id)
}
@@ -200,5 +202,12 @@
/>
</div>
</div>
<div class="flex p-4 flex-gap-2">
<span class="label">
<Label label={view.string.AutomationOnly} />
</span>
<Toggle bind:on={automationOnly} />
</div>
</div>
</Card>
@@ -16,7 +16,7 @@
import core, { Association } from '@hcengineering/core'
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import presentation, { getClient, MessageBox } from '@hcengineering/presentation'
import { Button, DropdownIntlItem, EditBox, IconDelete, Label, Modal, showPopup } from '@hcengineering/ui'
import { Button, DropdownIntlItem, EditBox, IconDelete, Label, Modal, showPopup, Toggle } from '@hcengineering/ui'
import view from '@hcengineering/view'
import setting from '../plugin'
import { clearSettingsStore } from '../store'
@@ -30,11 +30,13 @@
const classB = hierarchy.getClass(association.classB)
let nameA = association.nameA
let nameB = association.nameB
let automationOnly = association.automationOnly ?? false
async function save (): Promise<void> {
await client.diffUpdate(association, {
nameA,
nameB
nameB,
automationOnly
})
clearSettingsStore()
}
@@ -108,5 +110,11 @@
</div>
</div>
</div>
<div class="flex-between p-4 flex-gap-2">
<span class="label">
<Label label={view.string.AutomationOnly} />
</span>
<Toggle bind:on={automationOnly} />
</div>
</div>
</Modal>
@@ -13,30 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import card, { Card } from '@hcengineering/card'
import chat from '@hcengineering/chat'
import communication, { GuestCommunicationSettings } from '@hcengineering/communication'
import contact, { AvatarType, ensureEmployeeForPerson } from '@hcengineering/contact'
import { AvatarType } from '@hcengineering/contact'
import { EditableAvatar, getAccountClient } from '@hcengineering/contact-resources'
import core, {
type Account,
AccountRole,
AccountUuid,
Configuration,
getCurrentAccount,
pickPrimarySocialId,
readOnlyGuestAccountUuid,
Ref,
WorkspaceAccountPermission
} from '@hcengineering/core'
import core, { Configuration, WorkspaceAccountPermission } from '@hcengineering/core'
import { loginId } from '@hcengineering/login'
import { translateCB } from '@hcengineering/platform'
import { createQuery, getClient, MessageBox, uiContext } from '@hcengineering/presentation'
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import { WorkspaceSetting } from '@hcengineering/setting'
import {
Breadcrumb,
Button,
Component,
deviceOptionsStore as deviceInfo,
DropdownLabels,
type DropdownTextItem,
@@ -65,8 +51,6 @@
let oldName: string
let name: string = ''
let workspaceUrl = ''
let allowReadOnlyGuests: boolean
let allowGuestSignUp: boolean
let passwordAgingRule: number | undefined = undefined
const accountClient = getAccountClient()
@@ -87,8 +71,6 @@
workspaceUrl = res.url
oldName = res.name
name = oldName
allowReadOnlyGuests = res.allowReadOnlyGuest ?? false
allowGuestSignUp = res.allowGuestSignUp ?? false
passwordAgingRule = res.passwordAgingRule ?? undefined
loading = false
}
@@ -166,40 +148,6 @@
}
)
async function handleToggleReadonlyAccess (e: CustomEvent<boolean>): Promise<void> {
const enabled = e.detail
const guestUserInfo = await accountClient.updateAllowReadOnlyGuests(enabled)
allowReadOnlyGuests = enabled
if (guestUserInfo !== undefined) {
const guestAccount: Account = {
uuid: guestUserInfo.guestPerson.uuid as AccountUuid,
role: AccountRole.ReadOnlyGuest,
primarySocialId: pickPrimarySocialId(guestUserInfo.guestSocialIds)._id,
socialIds: guestUserInfo.guestSocialIds.map((si) => si._id),
fullSocialIds: guestUserInfo.guestSocialIds
}
const myAccount = getCurrentAccount()
const ctx = uiContext.newChild('connect', {})
await ensureEmployeeForPerson(
ctx,
myAccount,
guestAccount,
client,
guestUserInfo.guestSocialIds,
guestUserInfo.guestPerson
)
} else {
const readonlyEmployee = await client.findOne(contact.mixin.Employee, { personUuid: readOnlyGuestAccountUuid })
if (readonlyEmployee !== undefined) {
await client.update(readonlyEmployee, { active: false })
}
}
}
async function handleToggleGuestSignUp (e: CustomEvent<boolean>): Promise<void> {
await accountClient.updateAllowGuestSignUp(e.detail)
}
async function changePasswordAgingRules (val: number | undefined): Promise<void> {
passwordAgingRule = Math.max(val ?? 1, 1)
await accountClient.updatePasswordAgingRule(passwordAgingRule)
@@ -253,29 +201,6 @@
}
)
let existingGuestChatSettings: GuestCommunicationSettings | undefined = undefined
const query = createQuery()
$: query.query(communication.class.GuestCommunicationSettings, {}, (settings) => {
existingGuestChatSettings = settings[0]
})
async function onAllowedCardsChange (value: Ref<Card>[]): Promise<void> {
if (existingGuestChatSettings === undefined) {
await client.createDoc(communication.class.GuestCommunicationSettings, core.space.Workspace, {
allowedCards: value,
enabled: true
})
} else {
await client.updateDoc(
communication.class.GuestCommunicationSettings,
core.space.Workspace,
existingGuestChatSettings._id,
{ allowedCards: value, enabled: true }
)
}
}
const onSelected = (e: CustomEvent<string>): void => {
selected = e.detail
localStorage.setItem('firstDayOfWeek', `${e.detail}`)
@@ -373,43 +298,6 @@
</div>
</div>
<div class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.GuestAccess} /></div>
<div class="flex-row-center flex-gap-4">
<Label label={settingsRes.string.GuestAccessDescription} />
<Toggle
on={allowReadOnlyGuests}
on:change={(e) => {
void handleToggleReadonlyAccess(e)
}}
/>
</div>
<div class="flex-row-center flex-gap-4">
<Label label={settingsRes.string.GuestSignUpDescription} />
<Toggle
disabled={!allowReadOnlyGuests}
on={allowGuestSignUp}
on:change={(e) => {
void handleToggleGuestSignUp(e)
}}
/>
</div>
<div class="flex-row-center flex-gap-4">
<Label label={settingsRes.string.GuestChannelsDescription} />
<Component
is={card.component.CardArrayEditor}
props={{
_class: chat.masterTag.Thread,
value: existingGuestChatSettings !== undefined ? existingGuestChatSettings.allowedCards : [],
label: settingsRes.string.GuestChannelsArrayLabel,
onChange: onAllowedCardsChange
}}
/>
</div>
</div>
<div class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.AccessControl} /></div>
<div class="w-32">
@@ -12,27 +12,89 @@
// limitations under the License.
-->
<script lang="ts">
import core, { ModulePermissionGroup, type Doc, type Permission, type Ref } from '@hcengineering/core'
import card, { Card } from '@hcengineering/card'
import chat from '@hcengineering/chat'
import communication, { GuestCommunicationSettings } from '@hcengineering/communication'
import contact, { ensureEmployeeForPerson } from '@hcengineering/contact'
import { getAccountClient } from '@hcengineering/contact-resources'
import core, {
type Account,
AccountRole,
AccountUuid,
ModulePermissionGroup,
getCurrentAccount,
pickPrimarySocialId,
readOnlyGuestAccountUuid,
type Doc,
type Permission,
type Ref
} from '@hcengineering/core'
import { getEmbeddedLabel, getMetadata, type IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { createQuery, getClient, uiContext } from '@hcengineering/presentation'
import workbench, { type Application } from '@hcengineering/workbench'
import { Breadcrumb, Header, Icon, Label, Loading, Scroller, Toggle } from '@hcengineering/ui'
import {
Breadcrumb,
Component,
defineSeparators,
Header,
Icon,
Label,
Loading,
NavItem,
Scroller,
Separator,
Toggle,
twoPanelsSeparators
} from '@hcengineering/ui'
import setting from '@hcengineering/setting'
import { onMount } from 'svelte'
import settingsRes from '../plugin'
let loadingSettings = true
let loadingPermissions = true
let workspaceAppsReady = false
let loadingWorkspaceGuest = true
let allowReadOnlyGuests = false
let allowGuestSignUp = false
let existingGuestChatSettings: GuestCommunicationSettings | undefined = undefined
const accountClient = getAccountClient()
let moduleGroups: ModulePermissionGroup[] = []
let permissionsMap: Map<Ref<Permission>, Permission> = new Map<Ref<Permission>, Permission>()
let hiddenApplicationIds: Array<Ref<Application>> = []
const excludedApplicationIds = getMetadata(workbench.metadata.ExcludedApplications) ?? []
const communicationApiEnabled = getMetadata(communication.metadata.Enabled) === true
let guestPermissionsTab: 'guest' | 'anonymous' = 'guest'
const client = getClient()
const moduleGroupsQuery = createQuery()
const permissionsQuery = createQuery()
const hiddenAppsQuery = createQuery()
const guestCommunicationQuery = createQuery()
onMount(() => {
void (async (): Promise<void> => {
try {
const res = await accountClient.getWorkspaceInfo()
allowReadOnlyGuests = res.allowReadOnlyGuest ?? false
allowGuestSignUp = res.allowGuestSignUp ?? false
} finally {
loadingWorkspaceGuest = false
}
})()
})
$: {
if (communicationApiEnabled) {
guestCommunicationQuery.query(communication.class.GuestCommunicationSettings, {}, (settings) => {
existingGuestChatSettings = settings[0]
})
}
}
$: moduleGroupsQuery.query(core.class.ModulePermissionGroup, {}, (res) => {
moduleGroups = res as unknown as ModulePermissionGroup[]
@@ -62,13 +124,20 @@
workspaceApplications.map((application) => [application._id as Ref<Doc>, application])
)
$: loading = loadingSettings || loadingPermissions || !workspaceAppsReady
$: loading = loadingSettings || loadingPermissions || !workspaceAppsReady || loadingWorkspaceGuest
/** Ignore permission groups for applications not enabled in this workspace. */
$: visibleModuleGroups = moduleGroups.filter((group) => applicationsMap.has(group.application))
$: modulePermissionsRole = guestPermissionsTab === 'guest' ? AccountRole.Guest : AccountRole.ReadOnlyGuest
/** Module permission groups for the selected tab (Guest vs anonymous read-only). */
$: visibleModuleGroups = moduleGroups.filter(
(group) => applicationsMap.has(group.application) && group.role === modulePermissionsRole
)
$: sortedVisibleModuleGroups = [...visibleModuleGroups].sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity))
/** Anonymous (read-only guest) module rows are view-only until workspace allows anonymous guests. */
$: anonymousModulePermissionsReadOnly = guestPermissionsTab === 'anonymous' && !allowReadOnlyGuests
function getApplicationLabel (applicationId: Ref<Doc>): IntlString {
return applicationsMap.get(applicationId)?.label ?? getEmbeddedLabel(applicationId)
}
@@ -90,6 +159,7 @@
permissionId: Ref<Permission>,
enabled: boolean
): Promise<void> {
if (anonymousModulePermissionsReadOnly) return
if (!isModuleEnabled(group)) return
const disabled = getDisabledPermissions(group)
if (enabled) {
@@ -103,6 +173,7 @@
}
async function toggleModule (group: ModulePermissionGroup, enabled: boolean): Promise<void> {
if (anonymousModulePermissionsReadOnly) return
await client.updateDoc(core.class.ModulePermissionGroup, core.space.Model, group._id, {
enabled
} as any)
@@ -137,91 +208,245 @@
onPermissionToggle(group, permissionId, ev)
}
}
async function handleToggleReadonlyAccess (e: CustomEvent<boolean>): Promise<void> {
const enabled = e.detail
const guestUserInfo = await accountClient.updateAllowReadOnlyGuests(enabled)
allowReadOnlyGuests = enabled
if (guestUserInfo !== undefined) {
const guestAccount: Account = {
uuid: guestUserInfo.guestPerson.uuid as AccountUuid,
role: AccountRole.ReadOnlyGuest,
primarySocialId: pickPrimarySocialId(guestUserInfo.guestSocialIds)._id,
socialIds: guestUserInfo.guestSocialIds.map((si) => si._id),
fullSocialIds: guestUserInfo.guestSocialIds
}
const myAccount = getCurrentAccount()
const ctx = uiContext.newChild('connect', {})
await ensureEmployeeForPerson(
ctx,
myAccount,
guestAccount,
client,
guestUserInfo.guestSocialIds,
guestUserInfo.guestPerson
)
} else {
const readonlyEmployee = await client.findOne(contact.mixin.Employee, { personUuid: readOnlyGuestAccountUuid })
if (readonlyEmployee !== undefined) {
await client.update(readonlyEmployee, { active: false })
}
}
}
async function handleToggleGuestSignUp (e: CustomEvent<boolean>): Promise<void> {
await accountClient.updateAllowGuestSignUp(e.detail)
allowGuestSignUp = e.detail
}
async function onAllowedCardsChange (value: Ref<Card>[]): Promise<void> {
if (existingGuestChatSettings === undefined) {
await client.createDoc(communication.class.GuestCommunicationSettings, core.space.Workspace, {
allowedCards: value,
enabled: true
})
} else {
await client.updateDoc(
communication.class.GuestCommunicationSettings,
core.space.Workspace,
existingGuestChatSettings._id,
{ allowedCards: value, enabled: true }
)
}
}
defineSeparators('guestPermissionsSettings', twoPanelsSeparators)
</script>
<div class="hulyComponent">
<Header adaptive={'disabled'}>
<Breadcrumb label={setting.string.GuestPermissionsSettings} size={'large'} isCurrent />
<Breadcrumb
icon={setting.icon.GuestPermissions}
label={setting.string.GuestPermissionsSettings}
size={'large'}
isCurrent
/>
</Header>
<div class="hulyComponent-content__column content">
{#if loading}
<div class="w-full h-full flex-col-center justify-center">
<Loading />
</div>
{:else}
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content guestPermissionsRoot flex-col">
<section class="section">
<div class="sectionHeader">
<div class="sectionTitle">
<Label label={setting.string.GuestPermissionsModulePermissions} />
</div>
<div class="sectionHint">
<Label label={setting.string.GuestPermissionsModulePermissionsHint} />
</div>
</div>
<div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column navigation py-2">
<Scroller shrink>
<NavItem
icon={contact.icon.Person}
label={setting.string.GuestPermissionsTabGuest}
selected={guestPermissionsTab === 'guest'}
on:click={() => {
guestPermissionsTab = 'guest'
}}
/>
<NavItem
icon={contact.icon.Persona}
label={setting.string.GuestPermissionsTabAnonymousGuest}
selected={guestPermissionsTab === 'anonymous'}
on:click={() => {
guestPermissionsTab = 'anonymous'
}}
/>
</Scroller>
</div>
<div class="cardStack">
{#each sortedVisibleModuleGroups as group}
{@const app = getApplication(group.application)}
{@const moduleOn = isModuleEnabled(group)}
{@const permissionCount = (group.permissions ?? []).length}
<div class="permissionModuleCard" class:permissionModuleCard-off={!moduleOn}>
<div
class="permissionModuleCard-header"
class:permissionModuleCard-headerOnly={permissionCount === 0}
>
<div class="permissionModuleCard-headerMain">
{#if app}
<div class="appIcon appIcon-sm">
<Icon icon={app.icon} size={'small'} />
</div>
{:else}
<div class="appIcon appIcon-sm appIcon-placeholder" />
{/if}
<div class="permissionModuleCard-titles">
<div class="permissionModuleCard-name">
<Label label={getApplicationLabel(group.application)} />
<Separator name={'guestPermissionsSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content">
{#if loading}
<div class="w-full h-full flex-col-center justify-center">
<Loading />
</div>
{:else}
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content guestPermissionsRoot flex-col">
{#if guestPermissionsTab === 'anonymous'}
<section class="section">
<div class="sectionHeader">
<div class="sectionTitle">
<Label label={settingsRes.string.GuestAccess} />
</div>
</div>
<div class="guestAccessBlock">
<div class="guestAccessRow">
<div class="guestAccessRow-label">
<Label label={settingsRes.string.GuestAccessDescription} />
</div>
<div class="guestAccessRow-toggleCell">
<Toggle
on={allowReadOnlyGuests}
on:change={(e) => {
void handleToggleReadonlyAccess(e)
}}
/>
</div>
</div>
<div class="guestAccessRow">
<div class="guestAccessRow-label">
<Label label={settingsRes.string.GuestSignUpDescription} />
</div>
<div class="guestAccessRow-toggleCell">
<Toggle
disabled={!allowReadOnlyGuests}
on={allowGuestSignUp}
on:change={(e) => {
void handleToggleGuestSignUp(e)
}}
/>
</div>
</div>
{#if communicationApiEnabled}
<div class="guestAccessRow guestAccessRow--editor">
<div class="guestAccessRow-label">
<Label label={settingsRes.string.GuestChannelsDescription} />
</div>
<div class="guestAccessRow-editorCell">
<div class="guestAccessRow-editorInner">
<Component
is={card.component.CardArrayEditor}
props={{
_class: chat.masterTag.Thread,
value:
existingGuestChatSettings !== undefined ? existingGuestChatSettings.allowedCards : [],
label: settingsRes.string.GuestChannelsArrayLabel,
onChange: onAllowedCardsChange
}}
/>
</div>
</div>
</div>
<div class="permissionModuleCard-toggleCell">
<Toggle on={moduleOn} on:change={handleAccessToggle(group)} />
</div>
</div>
{#if permissionCount > 0}
<div class="permissionRows">
{#each group.permissions ?? [] as permissionId}
<div class="permissionRow">
<div class="permissionRow-label">
<Label label={getPermissionLabel(permissionId)} />
</div>
<div class="permissionRow-toggleCell">
<Toggle
disabled={!moduleOn}
on={isPermissionActive(group, permissionId)}
on:change={handlePermissionToggle(group, permissionId)}
/>
</div>
</div>
{/each}
</div>
{/if}
</div>
{/each}
{#if visibleModuleGroups.length === 0}
<div class="emptyState emptyState-block">—</div>
{/if}
</div>
</section>
</div>
</Scroller>
{/if}
</section>
{/if}
<section class="section">
<div class="sectionHeader">
<div class="sectionTitle">
<Label label={setting.string.GuestPermissionsApplicationPermissions} />
</div>
<div class="sectionHint">
{#if guestPermissionsTab === 'anonymous'}
<Label label={setting.string.GuestPermissionsAnonymousApplicationHint} />
{:else}
<Label label={setting.string.GuestPermissionsApplicationPermissionsHint} />
{/if}
</div>
</div>
<div class="cardStack" class:cardStack-readonly={anonymousModulePermissionsReadOnly}>
{#each sortedVisibleModuleGroups as group}
{@const app = getApplication(group.application)}
{@const moduleOn = isModuleEnabled(group)}
{@const permissionCount = (group.permissions ?? []).length}
<div class="permissionModuleCard" class:permissionModuleCard-off={!moduleOn}>
<div
class="permissionModuleCard-header"
class:permissionModuleCard-headerOnly={permissionCount === 0}
>
<div class="permissionModuleCard-headerMain">
{#if app}
<div class="appIcon appIcon-sm">
<Icon icon={app.icon} size={'small'} />
</div>
{:else}
<div class="appIcon appIcon-sm appIcon-placeholder" />
{/if}
<div class="permissionModuleCard-titles">
<div class="permissionModuleCard-name">
<Label label={getApplicationLabel(group.application)} />
</div>
</div>
</div>
<div class="permissionModuleCard-toggleCell">
<Toggle
disabled={anonymousModulePermissionsReadOnly}
on={moduleOn}
on:change={handleAccessToggle(group)}
/>
</div>
</div>
{#if permissionCount > 0}
<div class="permissionRows">
{#each group.permissions ?? [] as permissionId}
<div class="permissionRow">
<div class="permissionRow-label">
<Label label={getPermissionLabel(permissionId)} />
</div>
<div class="permissionRow-toggleCell">
<Toggle
disabled={!moduleOn || anonymousModulePermissionsReadOnly}
on={isPermissionActive(group, permissionId)}
on:change={handlePermissionToggle(group, permissionId)}
/>
</div>
</div>
{/each}
</div>
{/if}
</div>
{/each}
{#if visibleModuleGroups.length === 0}
<div class="emptyState emptyState-block">—</div>
{/if}
</div>
</section>
</div>
</Scroller>
{/if}
</div>
</div>
</div>
<style lang="scss">
/* Matches packages/ui Toggle width; shared with permission rows and guest access */
$toggleTrackWidth: 2.25rem;
.guestPermissionsRoot {
max-width: 40rem;
width: 100%;
@@ -229,6 +454,65 @@
gap: 2rem;
}
.guestAccessBlock {
display: flex;
flex-direction: column;
gap: 0;
padding-right: 1rem;
overflow: visible;
}
.guestAccessRow {
display: grid;
grid-template-columns: minmax(0, 1fr) #{$toggleTrackWidth};
align-items: center;
column-gap: 0.75rem;
min-height: 2.5rem;
padding: 0.625rem 0 0.625rem 0.25rem;
}
.guestAccessRow:not(:first-child) {
border-top: 1px solid var(--theme-navpanel-divider);
}
.guestAccessRow--editor {
align-items: start;
min-height: auto;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
overflow: visible;
}
/* Same two columns as toggle rows; editor sits in col 2 and grows left so its right edge matches toggles */
.guestAccessRow--editor .guestAccessRow-editorCell {
grid-column: 2;
grid-row: 1;
justify-self: end;
align-self: start;
width: max-content;
max-width: min(22rem, calc(100vw - 3rem));
min-width: 0;
overflow: visible;
}
.guestAccessRow-editorInner {
min-width: 0;
max-width: 100%;
}
.guestAccessRow-label {
min-width: 0;
color: var(--theme-content-color);
}
.guestAccessRow-toggleCell {
display: flex;
justify-content: center;
align-items: center;
width: $toggleTrackWidth;
justify-self: end;
}
.section {
display: flex;
flex-direction: column;
@@ -252,9 +536,6 @@
color: var(--theme-halfcontent-color);
}
/* Matches packages/ui Toggle width for a single aligned column */
$toggleTrackWidth: 2.25rem;
.cardStack {
display: flex;
flex-direction: column;
@@ -262,6 +543,10 @@
padding-top: 0.5rem;
}
.cardStack-readonly {
opacity: 0.85;
}
.appIcon {
display: flex;
align-items: center;
@@ -334,11 +619,6 @@
color: var(--theme-content-color);
}
.permissionModuleCard-meta {
font-size: 0.75rem;
color: var(--theme-halfcontent-color);
}
.permissionRows {
display: flex;
flex-direction: column;
+6 -2
View File
@@ -312,8 +312,11 @@ export default plugin(settingId, {
DefaultStartWithTranscription: '' as IntlString,
DefaultStartWithRecording: '' as IntlString,
GuestPermissionsSettings: '' as IntlString,
GuestPermissionsModulePermissions: '' as IntlString,
GuestPermissionsModulePermissionsHint: '' as IntlString,
GuestPermissionsApplicationPermissions: '' as IntlString,
GuestPermissionsApplicationPermissionsHint: '' as IntlString,
GuestPermissionsTabGuest: '' as IntlString,
GuestPermissionsTabAnonymousGuest: '' as IntlString,
GuestPermissionsAnonymousApplicationHint: '' as IntlString,
MailboxErrorInvalidName: '' as IntlString,
MailboxErrorDomainNotFound: '' as IntlString,
MailboxErrorNameRulesViolated: '' as IntlString,
@@ -356,6 +359,7 @@ export default plugin(settingId, {
AccountSettings: '' as Asset,
Owners: '' as Asset,
Members: '' as Asset,
GuestPermissions: '' as Asset,
Password: '' as Asset,
Setting: '' as Asset,
Integrations: '' as Asset,
@@ -82,17 +82,23 @@
: { docA: doc._id, docB: object._id, association: association._id }
const relation = await client.findOne(core.class.Relation, q)
const overrides = new Map()
const excludedActions: string[] = []
if (relation !== undefined) {
overrides.set(view.action.Delete, async (obj: Doc | Doc[], ev?: Event) => {
if (relation !== undefined) {
await client.remove(relation)
}
})
if (association.automationOnly) {
excludedActions.push(view.action.Delete)
} else {
overrides.set(view.action.Delete, async (obj: Doc | Doc[], ev?: Event) => {
if (relation !== undefined) {
await client.remove(relation)
}
})
}
}
showMenu(ev, { object: doc, overrides })
showMenu(ev, { object: doc, overrides, excludedActions })
}
function isAllowedToCreate (association: Association, docs: Doc[], direction: 'A' | 'B'): boolean {
if (association.automationOnly) return false
if (docs.length === 0 || association.type === 'N:N') return true
if (association.type === '1:1') return false
return direction === 'B'
@@ -62,8 +62,8 @@
if (g.enabled ?? true) return false
const role = getCurrentAccount().role
if (role === g.role) return true
// DocGuest / ReadOnlyGuest should also respect Guest module disables.
return (role === AccountRole.DocGuest || role === AccountRole.ReadOnlyGuest) && g.role === AccountRole.Guest
// DocGuest should also respect Guest module disables.
return role === AccountRole.DocGuest && g.role === AccountRole.Guest
})
.map((g) => g.application as Ref<Application>)
)