mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 01:55:00 +02:00
fix: restructure workspace general settings (#10120)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user