fix: restructure workspace general settings (#10120)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov
2025-10-21 20:49:53 +07:00
committed by GitHub
parent 0ee7e4ed6d
commit 092f5965fc
17 changed files with 290 additions and 83 deletions
@@ -0,0 +1,104 @@
<!--
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Timestamp } from '@hcengineering/core'
import presentation, { copyTextToClipboard } from '@hcengineering/presentation'
import view from '@hcengineering/view'
import { Button, Label, ticker } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import settings from '../plugin'
export let token: string
const isSecureContext = window.isSecureContext
const dispatch = createEventDispatcher()
let copiedTime: Timestamp | undefined
let copied = false
$: if (copiedTime !== undefined && copied && $ticker - copiedTime > 1000) {
copied = false
}
async function copy (): Promise<void> {
if (!isSecureContext) return
if (token === undefined) return
await copyTextToClipboard(token)
copied = true
copiedTime = Date.now()
}
</script>
<div class="antiPopup popup" class:secure={isSecureContext}>
<div class="overflow-label fs-title mb-4">
<Label label={settings.string.ApiToken} />
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="token" class:notSecure={!isSecureContext} class:over-underline={isSecureContext} on:click={copy}>
{token}
</div>
<div class="buttons">
<Button
label={presentation.string.Close}
size={'medium'}
kind={'primary'}
on:click={() => {
dispatch('close')
}}
/>
{#if isSecureContext}
<Button label={copied ? view.string.Copied : view.string.CopyToClipboard} size={'medium'} on:click={copy} />
{/if}
</div>
</div>
<style lang="scss">
.popup {
display: flex;
flex-direction: column;
padding: 1.75rem;
width: 30rem;
max-width: 40rem;
background: var(--popup-bg-color);
border-radius: 1.25rem;
user-select: none;
box-shadow: var(--popup-shadow);
.token {
margin: 1.75rem 0 0;
overflow-wrap: break-word;
&.notSecure {
user-select: text;
}
}
.buttons {
margin-top: 1.75rem;
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: flex-start;
align-items: center;
column-gap: 0.5rem;
}
}
</style>
@@ -172,7 +172,7 @@
<Scroller padding={'1.5rem'} noStretch>
{#if loading}
<Loading size={'small'} />
<Loading />
{:else if backupInfo == null}
<div class="no-backups">
<Label label={setting.string.BackupNoBackup} />
@@ -37,13 +37,12 @@
Header,
IconCheckmark,
IconClose,
IconDelete,
IconEdit,
Label,
Loading,
navigate,
Scroller,
showPopup,
Spinner,
themeStore,
Toggle
} from '@hcengineering/ui'
@@ -57,11 +56,13 @@
import communication, { GuestCommunicationSettings } from '@hcengineering/communication'
import card, { Card } from '@hcengineering/card'
import chat from '@hcengineering/chat'
import ApiTokenPopup from './ApiTokenPopup.svelte'
let loading = true
let isEditingName = false
let oldName: string
let name: string = ''
let workspaceUrl = ''
let allowReadOnlyGuests: boolean
let allowGuestSignUp: boolean
@@ -80,6 +81,7 @@
async function loadWorkspaceName (): Promise<void> {
const res = await accountClient.getWorkspaceInfo()
workspaceUrl = res.url
oldName = res.name
name = oldName
allowReadOnlyGuests = res.allowReadOnlyGuest ?? false
@@ -126,25 +128,26 @@
})
async function handleAvatarDone (): Promise<void> {
if (workspaceSettings === undefined) {
const existing = await client.findOne(settingsRes.class.WorkspaceSetting, { _id: settingsRes.ids.WorkspaceSetting })
if (existing !== undefined) {
const avatar = await avatarEditor.createAvatar()
// Remove old avatar if changed
if (existing.icon != null && existing.icon !== avatar.avatar) {
await avatarEditor.removeAvatar(existing.icon)
}
const icon = avatar.avatarType === AvatarType.IMAGE ? avatar.avatar : null
await client.diffUpdate(existing, { icon })
} else {
const avatar = await avatarEditor.createAvatar()
await client.createDoc(
settingsRes.class.WorkspaceSetting,
core.space.Workspace,
{ icon: avatar.avatar },
settingsRes.ids.WorkspaceSetting
)
return
}
const avatar = await avatarEditor.createAvatar()
if (workspaceSettings.icon != null && workspaceSettings.icon !== avatar.avatar) {
// Different avatar
await avatarEditor.removeAvatar(workspaceSettings.icon)
}
await client.update(workspaceSettings, {
icon: avatar.avatar
})
}
const permissionConfigurationQuery = createQuery()
@@ -193,6 +196,11 @@
await accountClient.updateAllowGuestSignUp(e.detail)
}
async function handleGenerateApiToken (): Promise<void> {
const { token } = await accountClient.selectWorkspace(workspaceUrl)
showPopup(ApiTokenPopup, { token })
}
function handleTogglePermissions (): void {
const newState = !arePermissionsDisabled
showPopup(MessageBox, {
@@ -268,11 +276,13 @@
<div class="hulyComponent">
<Header adaptive={'disabled'}>
<Breadcrumb icon={settingsRes.icon.Setting} label={settingsRes.string.General} size={'large'} isCurrent />
<Breadcrumb icon={settingsRes.icon.Setting} label={settingsRes.string.WorkspaceSettings} size={'large'} isCurrent />
</Header>
<div class="hulyComponent-content__column content">
{#if loading}
<Spinner size={'small'} />
<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 flex-col flex-gap-4">
@@ -280,10 +290,11 @@
<div class="ws">
<EditableAvatar
person={{
avatarType: AvatarType.IMAGE,
avatarType: workspaceSettings?.icon !== undefined ? AvatarType.IMAGE : AvatarType.COLOR,
avatar: workspaceSettings?.icon
}}
size="medium"
{name}
bind:this={avatarEditor}
on:done={handleAvatarDone}
imageOnly
@@ -307,13 +318,8 @@
{#if isEditingName}
<Button icon={IconClose} kind="ghost" size="small" on:click={handleCancelEditName} />
{/if}
<Button
icon={IconDelete}
kind="dangerous"
on:click={handleDelete}
showTooltip={{ label: settingsRes.string.DeleteWorkspace }}
/>
</div>
<div class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.Calendar} /></div>
<div class="flex-row-center flex-gap-4">
@@ -328,46 +334,80 @@
/>
</div>
</div>
<div class="title mt-6"><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 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-row-center flex-gap-4">
<Label label={settingsRes.string.GuestSignUpDescription} />
<Toggle
disabled={!allowReadOnlyGuests}
on={allowGuestSignUp}
on:change={(e) => {
void handleToggleGuestSignUp(e)
}}
/>
<div class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.AccessControl} /></div>
<div class="w-32">
<Button
kind="regular"
label={arePermissionsDisabled
? settingsRes.string.EnablePermissions
: settingsRes.string.DisablePermissions}
on:click={handleTogglePermissions}
/>
</div>
</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 class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.ApiAccess} /></div>
<div class="w-32">
<Button
label={settingsRes.string.GenerateApiToken}
kind="regular"
disabled={workspaceUrl === ''}
showTooltip={{ label: settingsRes.string.GenerateApiToken }}
on:click={handleGenerateApiToken}
/>
</div>
</div>
<div class="delete">
<Button
kind="regular"
label={arePermissionsDisabled
? settingsRes.string.EnablePermissions
: settingsRes.string.DisablePermissions}
on:click={handleTogglePermissions}
/>
<div class="flex-col flex-gap-4 mt-6">
<div class="title"><Label label={settingsRes.string.DangerZone} /></div>
<div class="w-32">
<Button
label={settingsRes.string.DeleteWorkspace}
kind="dangerous"
on:click={handleDelete}
showTooltip={{ label: settingsRes.string.DeleteWorkspace }}
/>
</div>
</div>
</div>
</Scroller>
@@ -389,8 +429,4 @@
.editBox {
width: 16rem;
}
.delete {
width: 6rem;
}
</style>
@@ -131,12 +131,6 @@
<Label label={setting.string.Primary} />
</div>
{/if}
{#if canRelease}
<div class="on-hover">
<Button label={setting.string.Release} kind="ghost" on:click={handleRelease} />
</div>
{/if}
</div>
<style lang="scss">