Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions models/process/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,32 @@ export function defineMethods (builder: Builder): void {
},
process.method.UnlockSection
)

builder.createDoc(
process.class.Method,
core.space.Model,
{
label: process.string.LockField,
objectClass: card.class.Card,
editor: process.component.LockFieldEditor,
presenter: process.component.LockFieldPresenter,
requiredParams: ['value'],
createdContext: null
},
process.method.LockField
)

builder.createDoc(
process.class.Method,
core.space.Model,
{
label: process.string.UnlockField,
objectClass: card.class.Card,
editor: process.component.LockFieldEditor,
presenter: process.component.UnLockFieldPresenter,
requiredParams: ['value'],
createdContext: null
},
process.method.UnlockField
)
}
8 changes: 8 additions & 0 deletions models/server-process/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ export function createModel (builder: Builder): void {
func: serverProcess.func.UnlockSection
})

builder.mixin(process.method.LockField, process.class.Method, serverProcess.mixin.MethodImpl, {
func: serverProcess.func.LockField
})

builder.mixin(process.method.UnlockField, process.class.Method, serverProcess.mixin.MethodImpl, {
func: serverProcess.func.UnlockField
})

builder.mixin(process.function.FirstValue, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
func: serverProcess.transform.FirstValue
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
{_class}
{object}
{showHeader}
readonly={readonly || !canChange(key.attr, $permissionsStore)}
readonly={readonly || !canChange(key.attr, $permissionsStore) || object.readonlyFields?.includes(key.key)}
withIcon
on:update
/>
Expand Down
2 changes: 1 addition & 1 deletion plugins/card-resources/src/components/EditCardNew.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
}
})

$: _readonly = (readonly || doc?.readonly) ?? false
$: _readonly = (readonly || doc?.readonly || doc?.readonlyFields?.includes('title')) ?? false
$: updatePermissionForbidden = doc && !canChangeDoc(doc?._class, doc?.space, $permissionsStore)
</script>

Expand Down
1 change: 1 addition & 0 deletions plugins/card/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface Card extends Doc, IconProps, VersionableDoc {
peerId?: string

readonlySections?: Ref<MasterTag>[]
readonlyFields?: string[]
}

export interface CardSpace extends TypedSpace {
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@
"ActionType": "Typ",
"ApproveAction": "Schválit",
"ReviewAction": "Zkontrolovat",
"Review": "Zkontrolovat"
"Review": "Zkontrolovat",
"LockField": "Zamknout pole",
"UnlockField": "Odemknout pole"
},
"error": {
"MethodNotFound": "Metoda nenalezena: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@
"ActionType": "Typ",
"ApproveAction": "Genehmigen",
"ReviewAction": "Prüfen",
"Review": "Prüfen"
"Review": "Prüfen",
"LockField": "Feld sperren",
"UnlockField": "Feld entsperren"
},
"error": {
"MethodNotFound": "Methode nicht gefunden: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Type",
"ApproveAction": "Approve",
"ReviewAction": "Review",
"Review": "Review"
"Review": "Review",
"LockField": "Lock field",
"UnlockField": "Unlock field"
},
"error": {
"MethodNotFound": "Method not found: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Tipo",
"ApproveAction": "Aprobar",
"ReviewAction": "Revisar",
"Review": "Revisar"
"Review": "Revisar",
"LockField": "Bloquear campo",
"UnlockField": "Desbloquear campo"
},
"error": {
"MethodNotFound": "Método no encontrado: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Type",
"ApproveAction": "Approuver",
"ReviewAction": "Revoir",
"Review": "Revoir"
"Review": "Revoir",
"LockField": "Verrouiller le champ",
"UnlockField": "Déverrouiller le champ"
},
"error": {
"MethodNotFound": "Méthode introuvable : {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Tipo",
"ApproveAction": "Approva",
"ReviewAction": "Revisa",
"Review": "Revisa"
"Review": "Revisa",
"LockField": "Blocca campo",
"UnlockField": "Sblocca campo"
},
"error": {
"MethodNotFound": "Metodo non trovato: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@
"ActionType": "タイプ",
"ApproveAction": "承認",
"ReviewAction": "レビュー",
"Review": "レビュー"
"Review": "レビュー",
"LockField": "フィールドをロック",
"UnlockField": "フィールドのロックを解除"
},
"error": {
"MethodNotFound": "メソッドが見つかりません: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@
"ActionType": "Tipo",
"ApproveAction": "Aprovar",
"ReviewAction": "Revisar",
"Review": "Revisar"
"Review": "Revisar",
"LockField": "Bloquear campo",
"UnlockField": "Desbloquear campo"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Tipo",
"ApproveAction": "Aprovar",
"ReviewAction": "Revisar",
"Review": "Revisar"
"Review": "Revisar",
"LockField": "Bloquear campo",
"UnlockField": "Desbloquear campo"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "Тип",
"ApproveAction": "Утверждение",
"ReviewAction": "Рецензирование",
"Review": "Рецензировать"
"Review": "Рецензировать",
"LockField": "Заблокировать поле",
"UnlockField": "Разблокировать поле"
},
"error": {
"MethodNotFound": "Метод не найден: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@
"ActionType": "Tür",
"ApproveAction": "Onayla",
"ReviewAction": "Gözden geçir",
"Review": "Gözden geçir"
"Review": "Gözden geçir",
"LockField": "Alanı kilitle",
"UnlockField": "Alanı kilitle"
},
"error": {
"MethodNotFound": "Metod bulunamadı: {methodId}",
Expand Down
4 changes: 3 additions & 1 deletion plugins/process-assets/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@
"ActionType": "类型",
"ApproveAction": "批准",
"ReviewAction": "审查",
"Review": "审查"
"Review": "审查",
"LockField": "锁定字段",
"UnlockField": "解锁字段"
},
"error": {
"MethodNotFound": "找不到方法:{methodId}",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<!--
// Copyright © 2026 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 cardPlugin, { Card, MasterTag, Tag } from '@hcengineering/card'
import core, { AnyAttribute, Class, Ref } from '@hcengineering/core'
import { Process, Step } from '@hcengineering/process'
import { Button, eventToHTMLElement, IconClose, Label, SelectPopup, showPopup, tooltip } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import TagSelector from './TagSelector.svelte'
import presentation, { getClient } from '@hcengineering/presentation'

export let process: Process
export let step: Step<Card>

const params = step.params

const client = getClient()
const hierarchy = client.getHierarchy()

const dispatch = createEventDispatcher()

function getKeys (_class: Ref<Class<MasterTag>>): AnyAttribute[] {
const ignoreKeys = ['_class', 'content', 'parent', 'attachments', 'todos']
const attributes = hierarchy.getAllAttributes(_class, core.class.Doc)
const res: AnyAttribute[] = []
for (const [key, attr] of attributes) {
if (attr.hidden === true) continue
if (ignoreKeys.includes(key)) continue
res.push(attr)
}
return res
}

function change (value: string[]): void {
params.value = value
step.params = params
dispatch('change', step)
}

let keys: string[] = params.value ?? []

$: allAttrs = getKeys(process.masterTag)
$: possibleAttrs = allAttrs.filter((attr) => !keys.includes(attr.name))

function addKey (key: string): void {
keys = [...keys, key]
change(keys)
}

function onAdd (e: MouseEvent): void {
showPopup(
SelectPopup,
{
value: possibleAttrs.map((p) => {
return { id: p.name, label: p.label }
})
},
eventToHTMLElement(e),
(res) => {
if (res != null) {
addKey(res)
}
}
)
}

function remove (key: string): void {
keys = keys.filter((k) => k !== key)
change(keys)
}
</script>

<div class="grid">
{#each keys as key (key)}
{@const attribute = allAttrs.find((attr) => attr.name === key)}
{#if attribute}
<span
class="labelOnPanel"
use:tooltip={{
props: { label: attribute.label }
}}
>
<Label label={attribute.label} />
</span>
<div class="button flex-row-center">
<Button icon={IconClose} kind="ghost" on:click={() => { remove(key) }} />
</div>
{/if}
{/each}
</div>
{#if possibleAttrs.length > 0}
<div class="flex-center mt-4">
<Button label={presentation.string.Add} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
</div>
{/if}

<style lang="scss">
.grid {
display: grid;
grid-template-columns: 1.5fr 1.5fr;
grid-auto-rows: minmax(2rem, max-content);
justify-content: start;
align-items: center;
row-gap: 0.5rem;
column-gap: 1rem;
margin: 0.25rem 2rem 0;
width: calc(100% - 4rem);
height: min-content;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--
// Copyright © 2026 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 { getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import plugin from '../../plugin'
import { Process } from '@hcengineering/process'

export let process: Process
export let params: Record<string, any>
export let unlock: boolean = false

const client = getClient()
const h = client.getHierarchy()

const keys = params.value ?? []

const attrs = keys.map((key: string) => h.getAttribute(process.masterTag, key))
</script>

<Label label={unlock ? plugin.string.UnlockField : plugin.string.LockField} />:
{#each attrs as attr}
<Label label={attr.label} />
{/each}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
// Copyright © 2026 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 { Process } from '@hcengineering/process'
import LockFieldPresenter from './LockFieldPresenter.svelte'

export let process: Process
export let params: Record<string, any>
</script>

<LockFieldPresenter {process} {params} unlock />
Loading
Loading