Skip to content

Commit c448cc9

Browse files
wrdhubcharlag
andcommitted
Allow for editing of groupInfo and groupSettings name
After the group was shared it was not possible to change the original name anymore, neither for the owner nor for participants. Show groupInfo name in the editing dialog for shared groups Add GroupSettingsModel for editing the name of groups Used in ContactList, Templates, and Calendar Do not set name on groupSettings when accepting invitation, so that groupInfo name take priority Closes: #8639 Co-authored-by: ivk <[email protected]>
1 parent c679c3e commit c448cc9

30 files changed

+774
-277
lines changed

src/calendar-app/calendar/gui/EditCalendarDialog.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Stream from "mithril/stream"
55
import { TextField, TextFieldType } from "../../../common/gui/base/TextField.js"
66
import { lang, type TranslationKey } from "../../../common/misc/LanguageViewModel.js"
77
import type { TranslationKeyType } from "../../../common/misc/TranslationKey.js"
8-
import { deepEqual, isNotNull } from "@tutao/tutanota-utils"
8+
import { clone, deepEqual, isNotNull } from "@tutao/tutanota-utils"
99
import { AlarmInterval, CalendarType, isExternalCalendarType, isNormalCalendarType } from "../../../common/calendar/date/CalendarUtils.js"
1010
import { RemindersEditor } from "./RemindersEditor.js"
1111
import { checkURLString, isIcal } from "../../../common/calendar/gui/ImportExportUtils.js"
@@ -15,16 +15,18 @@ import { DEFAULT_ERROR } from "../../../common/api/common/TutanotaConstants.js"
1515
import { LoginButton } from "../../../common/gui/base/buttons/LoginButton.js"
1616
import { ColorPickerView } from "../../../common/gui/base/colorPicker/ColorPickerView"
1717
import { generateRandomColor } from "./CalendarGuiUtils.js"
18+
import { GroupNameData } from "../../../common/sharing/model/GroupSettingsModel"
19+
import { GroupSettingNameInputFields } from "../../../common/sharing/view/GroupSettingNameInputFields"
1820

1921
export type CalendarProperties = {
20-
name: string
22+
nameData: GroupNameData
2123
color: string
2224
alarms: AlarmInterval[]
2325
sourceUrl: string | null
2426
}
2527

26-
export const defaultCalendarProperties: CalendarProperties = {
27-
name: "",
28+
export const defaultCalendarProperties: Readonly<CalendarProperties> & { readonly nameData: Readonly<GroupNameData> } = {
29+
nameData: { kind: "single", name: "" },
2830
color: "",
2931
alarms: [],
3032
sourceUrl: "",
@@ -64,28 +66,23 @@ function sourceUrlInputField(urlStream: Stream<string>, errorMessageStream: Stre
6466
}
6567

6668
function createEditCalendarComponent(
67-
nameStream: Stream<string>,
69+
nameData: GroupNameData,
6870
colorStream: Stream<string>,
69-
shared: boolean,
7071
calendarType: CalendarType,
7172
alarms: AlarmInterval[],
7273
urlStream: Stream<string>,
7374
errorMessageStream: Stream<string>,
7475
) {
7576
return m.fragment({}, [
76-
m(TextField, {
77-
value: nameStream(),
78-
oninput: nameStream,
79-
label: "calendarName_label",
80-
}),
77+
m(GroupSettingNameInputFields, { groupNameData: nameData }),
8178
m(".small.mt.mb-xs", lang.get("color_label")),
8279
m(ColorPickerView, {
8380
value: colorStream(),
8481
onselect: (color: string) => {
8582
colorStream(color)
8683
},
8784
}),
88-
!shared && isNormalCalendarType(calendarType)
85+
nameData.kind === "single" && isNormalCalendarType(calendarType)
8986
? m(RemindersEditor, {
9087
alarms,
9188
addAlarm: (alarm: AlarmInterval) => {
@@ -106,7 +103,6 @@ function createEditCalendarComponent(
106103
export interface CreateEditDialogAttrs {
107104
calendarType: CalendarType
108105
titleTextId: TranslationKeyType
109-
shared: boolean
110106
okAction: (dialog: Dialog, calendarProperties: CalendarProperties, calendarModel?: CalendarModel) => unknown
111107
okTextId: TranslationKeyType
112108
warningMessage?: () => Children
@@ -118,11 +114,10 @@ export interface CreateEditDialogAttrs {
118114
export function showCreateEditCalendarDialog({
119115
calendarType,
120116
titleTextId,
121-
shared,
122117
okAction,
123118
okTextId,
124119
warningMessage,
125-
calendarProperties: { name, color, alarms, sourceUrl } = defaultCalendarProperties,
120+
calendarProperties: { nameData, color, alarms, sourceUrl } = clone(defaultCalendarProperties),
126121
isNewCalendar = true,
127122
calendarModel,
128123
}: CreateEditDialogAttrs) {
@@ -132,7 +127,6 @@ export function showCreateEditCalendarDialog({
132127
color = generateRandomColor()
133128
}
134129

135-
const nameStream = stream(name)
136130
const colorStream = stream(color)
137131
const urlStream = stream(sourceUrl ?? "")
138132
const errorMessageStream = stream(DEFAULT_ERROR)
@@ -155,7 +149,7 @@ export function showCreateEditCalendarDialog({
155149
okAction(
156150
dialog,
157151
{
158-
name: nameStream(),
152+
nameData,
159153
color: colorStream().substring(1),
160154
alarms,
161155
sourceUrl: urlStream().trim(),
@@ -203,7 +197,7 @@ export function showCreateEditCalendarDialog({
203197
view: () =>
204198
m(".flex.col", [
205199
warningMessage ? warningMessage() : null,
206-
createEditCalendarComponent(nameStream, colorStream, shared, calendarType, alarms, urlStream, errorMessageStream),
200+
createEditCalendarComponent(nameData, colorStream, calendarType, alarms, urlStream, errorMessageStream),
207201
]),
208202
},
209203
okAction: doAction,

src/calendar-app/calendar/view/CalendarView.ts

Lines changed: 50 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
Contact,
2424
ContactTypeRef,
2525
createDefaultAlarmInfo,
26-
createGroupSettings,
2726
GroupSettings,
2827
UserSettingsGroupRoot,
2928
} from "../../../common/api/entities/tutanota/TypeRefs.js"
@@ -101,7 +100,7 @@ import { CalendarEventPreviewViewModel } from "../gui/eventpopup/CalendarEventPr
101100
import { client } from "../../../common/misc/ClientDetector.js"
102101
import { FloatingActionButton } from "../../gui/FloatingActionButton.js"
103102
import { Icon, IconSize, progressIcon } from "../../../common/gui/base/Icon.js"
104-
import { Group, GroupInfo, User } from "../../../common/api/entities/sys/TypeRefs.js"
103+
import { Group, User } from "../../../common/api/entities/sys/TypeRefs.js"
105104
import { formatDate, formatTime } from "../../../common/misc/Formatter.js"
106105
import { getExternalCalendarName, parseCalendarStringData, SyncStatus } from "../../../common/calendar/gui/ImportExportUtils.js"
107106
import type { ParsedEvent } from "../../../common/calendar/gui/CalendarImporter.js"
@@ -945,7 +944,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
945944

946945
private showCreateCalendarDialog(calendarType: CalendarType) {
947946
const createNormalCalendar = async (dialog: Dialog, properties: CalendarProperties, calendarModel: CalendarModel) => {
948-
await calendarModel.createCalendar(properties.name, properties.color, properties.alarms, null)
947+
await calendarModel.createCalendar(properties.nameData.name, properties.color, properties.alarms, null)
949948
dialog.close()
950949
}
951950
const createExternalCalendar = async (dialog: Dialog, properties: CalendarProperties, calendarModel: CalendarModel) => {
@@ -979,7 +978,6 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
979978
showCreateEditCalendarDialog({
980979
calendarType,
981980
titleTextId: "add_action",
982-
shared: false,
983981
okAction: createNormalCalendar,
984982
okTextId: "save_action",
985983
calendarModel: this.viewModel.getCalendarModel(),
@@ -989,7 +987,6 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
989987
showCreateEditCalendarDialog({
990988
calendarType,
991989
titleTextId: "newCalendarSubscriptionsDialog_title",
992-
shared: false,
993990
okAction: createExternalCalendar,
994991
okTextId: "subscribe_action",
995992
warningMessage: () => m(".smaller.content-fg", lang.get("externalCalendarInfo_msg")),
@@ -1032,16 +1029,11 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
10321029
})
10331030

10341031
return filteredCalendarInfos.map(([_, calendarInfo]) => {
1035-
return this.renderCalendarItem(calendarInfo, calendarInfo.shared, toggleHidden, this.viewModel.hiddenCalendars.has(calendarInfo.group._id))
1032+
return this.renderCalendarItem(calendarInfo, toggleHidden, this.viewModel.hiddenCalendars.has(calendarInfo.group._id))
10361033
})
10371034
}
10381035

1039-
private renderCalendarItem(
1040-
calendarInfo: CalendarInfo,
1041-
shared: boolean,
1042-
toggleHidden: (viewModel: CalendarViewModel, groupRootId: string) => void,
1043-
isHidden: boolean,
1044-
) {
1036+
private renderCalendarItem(calendarInfo: CalendarInfo, toggleHidden: (viewModel: CalendarViewModel, groupRootId: string) => void, isHidden: boolean) {
10451037
const { userSettingsGroupRoot } = locator.logins.getUserController()
10461038
const existingGroupSettings = userSettingsGroupRoot.groupSettings.find((gc) => gc.group === calendarInfo.groupInfo.group) ?? null
10471039

@@ -1111,7 +1103,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
11111103
},
11121104
})
11131105
: null,
1114-
this.createCalendarActionDropdown(calendarInfo, colorValue ?? defaultCalendarColor, existingGroupSettings, userSettingsGroupRoot, shared),
1106+
this.createCalendarActionDropdown(calendarInfo, colorValue ?? defaultCalendarColor, existingGroupSettings, userSettingsGroupRoot),
11151107
])
11161108
}
11171109

@@ -1120,7 +1112,6 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
11201112
colorValue: string,
11211113
existingGroupSettings: GroupSettings | null,
11221114
userSettingsGroupRoot: UserSettingsGroupRoot,
1123-
sharedCalendar: boolean,
11241115
): Children {
11251116
const { group, groupInfo, groupRoot, isExternal } = calendarInfo
11261117
const user = locator.logins.getUserController().user
@@ -1136,7 +1127,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
11361127
label: "edit_action",
11371128
icon: Icons.Edit,
11381129
size: ButtonSize.Compact,
1139-
click: () => this.onPressedEditCalendar(groupInfo, colorValue, existingGroupSettings, userSettingsGroupRoot, sharedCalendar),
1130+
click: () => this.onPressedEditCalendar(calendarInfo, colorValue, existingGroupSettings, userSettingsGroupRoot),
11401131
},
11411132
!isExternal && !isClientOnly
11421133
? {
@@ -1146,7 +1137,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
11461137
if (locator.logins.getUserController().isFreeAccount()) {
11471138
showNotAvailableForFreeDialog()
11481139
} else {
1149-
showGroupSharingDialog(groupInfo, sharedCalendar)
1140+
showGroupSharingDialog(groupInfo, calendarInfo.shared)
11501141
}
11511142
},
11521143
}
@@ -1166,7 +1157,7 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
11661157
const alarmInfoList = user.alarmInfoList
11671158
if (alarmInfoList) {
11681159
exportCalendar(
1169-
getSharedGroupName(groupInfo, locator.logins.getUserController(), sharedCalendar),
1160+
getSharedGroupName(groupInfo, locator.logins.getUserController(), calendarInfo.shared),
11701161
groupRoot,
11711162
alarmInfoList.alarms,
11721163
new Date(),
@@ -1234,13 +1225,13 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
12341225
})
12351226
}
12361227

1237-
private onPressedEditCalendar(
1238-
groupInfo: GroupInfo,
1228+
private async onPressedEditCalendar(
1229+
calendarInfo: CalendarInfo,
12391230
colorValue: string,
12401231
existingGroupSettings: GroupSettings | null,
12411232
userSettingsGroupRoot: UserSettingsGroupRoot,
1242-
shared: boolean,
12431233
) {
1234+
const { groupInfo } = calendarInfo
12441235
if (isClientOnlyCalendar(groupInfo.group) && !this.viewModel.isNewPaidPlan) {
12451236
showPlanUpgradeRequiredDialog(NewPaidPlans)
12461237
return
@@ -1249,11 +1240,10 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
12491240
showCreateEditCalendarDialog({
12501241
calendarType: getCalendarType(existingGroupSettings, groupInfo),
12511242
titleTextId: "edit_action",
1252-
shared,
1253-
okAction: (dialog, properties) => this.handleModifiedCalendar(dialog, properties, shared, groupInfo, existingGroupSettings, userSettingsGroupRoot),
1243+
okAction: (dialog, properties) => this.handleModifiedCalendar(dialog, properties, calendarInfo, existingGroupSettings, userSettingsGroupRoot),
12541244
okTextId: "save_action",
12551245
calendarProperties: {
1256-
name: getSharedGroupName(groupInfo, locator.logins.getUserController(), shared),
1246+
nameData: await this.viewModel.getCalendarNameData(calendarInfo.groupInfo),
12571247
color: colorValue.substring(1),
12581248
alarms: existingGroupSettings?.defaultAlarmsList.map((alarm) => parseAlarmInterval(alarm.trigger)) ?? [],
12591249
sourceUrl: existingGroupSettings?.sourceUrl ?? null,
@@ -1266,58 +1256,53 @@ export class CalendarView extends BaseTopLevelView implements TopLevelView<Calen
12661256
private handleModifiedCalendar(
12671257
dialog: Dialog,
12681258
properties: CalendarProperties,
1269-
shared: boolean,
1270-
groupInfo: GroupInfo,
1259+
calendarInfo: CalendarInfo,
12711260
existingGroupSettings: GroupSettings | null,
12721261
userSettingsGroupRoot: UserSettingsGroupRoot,
12731262
) {
1263+
const { groupInfo, shared, userIsOwner } = calendarInfo
1264+
12741265
const clientOnlyCalendar = isClientOnlyCalendar(groupInfo.group)
1275-
if (!shared && !clientOnlyCalendar) {
1276-
// User is the owner, so we update the entity instead of groupSettings
1277-
groupInfo.name = properties.name
1278-
locator.entityClient.update(groupInfo)
1279-
}
12801266

1281-
const shouldSyncExternal = !!(existingGroupSettings && hasSourceUrl(existingGroupSettings) && existingGroupSettings.sourceUrl !== properties.sourceUrl)
1282-
const alarms = properties.alarms.map((alarm) => createDefaultAlarmInfo({ trigger: serializeAlarmInterval(alarm) }))
1283-
// color always set for existing calendar
1284-
if (existingGroupSettings) {
1285-
existingGroupSettings.color = properties.color
1286-
existingGroupSettings.name = shared && properties.name !== groupInfo.name ? properties.name : null
1287-
existingGroupSettings.defaultAlarmsList = alarms
1288-
existingGroupSettings.sourceUrl = properties.sourceUrl
1289-
} else if (clientOnlyCalendar) {
1290-
this.viewModel.handleClientOnlyUpdate(groupInfo, downcast({ name: properties.name, color: properties.color }))
1267+
if (clientOnlyCalendar) {
1268+
this.viewModel.handleClientOnlyUpdate(groupInfo, { name: properties.nameData.name, color: properties.color })
12911269
dialog.close()
12921270
return this.viewModel.redraw(undefined)
12931271
} else {
1294-
const newGroupSettings = createGroupSettings({
1295-
group: groupInfo.group,
1296-
color: properties.color,
1297-
name: shared && properties.name !== groupInfo.name ? properties.name : null,
1298-
defaultAlarmsList: alarms,
1299-
sourceUrl: properties.sourceUrl,
1300-
})
1301-
1302-
userSettingsGroupRoot.groupSettings.push(newGroupSettings)
1303-
}
1272+
if (userIsOwner) {
1273+
// if it is a shared calendar and the shared name has been changed the entity needs to be updated
1274+
// the name on the entity is what is shared with everyone
1275+
this.viewModel.setCalendarGroupInfoName(groupInfo, properties.nameData.name)
1276+
}
13041277

1305-
locator.entityClient
1306-
.update(userSettingsGroupRoot)
1307-
.then(() => {
1308-
if (shouldSyncExternal)
1309-
this.viewModel.forceSyncExternal(existingGroupSettings)?.catch(async (e) => {
1310-
showSnackBar({
1311-
message: lang.makeTranslation("exception_msg", e.message),
1312-
button: {
1313-
label: "ok_action",
1314-
click: noOp,
1315-
},
1316-
waitingTime: 500,
1278+
const shouldSyncExternal = !!(
1279+
existingGroupSettings &&
1280+
hasSourceUrl(existingGroupSettings) &&
1281+
existingGroupSettings.sourceUrl !== properties.sourceUrl
1282+
)
1283+
const alarms = properties.alarms.map((alarm) => createDefaultAlarmInfo({ trigger: serializeAlarmInterval(alarm) }))
1284+
this.viewModel
1285+
.setCalendarGroupSettings(groupInfo, {
1286+
color: properties.color,
1287+
name: properties.nameData.kind === "shared" ? properties.nameData.customName : null,
1288+
defaultAlarmsList: alarms,
1289+
sourceUrl: properties.sourceUrl,
1290+
})
1291+
.then(() => {
1292+
if (shouldSyncExternal)
1293+
this.viewModel.forceSyncExternal(existingGroupSettings)?.catch(async (e) => {
1294+
showSnackBar({
1295+
message: lang.makeTranslation("exception_msg", e.message),
1296+
button: {
1297+
label: "ok_action",
1298+
click: noOp,
1299+
},
1300+
waitingTime: 500,
1301+
})
13171302
})
1318-
})
1319-
})
1320-
.catch(ofClass(LockedError, noOp))
1303+
})
1304+
.catch(ofClass(LockedError, noOp))
1305+
}
13211306
dialog.close()
13221307
}
13231308

0 commit comments

Comments
 (0)