@things-factory/figure-service 10.1.178 → 10.1.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +6 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/routers/figure-mesh-router.js +4 -1
- package/dist-server/routers/figure-mesh-router.js.map +1 -1
- package/dist-server/routers/figure-thumbnail-router.js +3 -7
- package/dist-server/routers/figure-thumbnail-router.js.map +1 -1
- package/dist-server/service/figure/figure-categories.d.ts +10 -0
- package/dist-server/service/figure/figure-categories.js +53 -0
- package/dist-server/service/figure/figure-categories.js.map +1 -0
- package/dist-server/service/figure/figure-format.d.ts +18 -1
- package/dist-server/service/figure/figure-format.js +28 -1
- package/dist-server/service/figure/figure-format.js.map +1 -1
- package/dist-server/service/figure/figure-mutation.d.ts +8 -2
- package/dist-server/service/figure/figure-mutation.js +83 -34
- package/dist-server/service/figure/figure-mutation.js.map +1 -1
- package/dist-server/service/figure/figure-namespace-entity.d.ts +24 -0
- package/dist-server/service/figure/figure-namespace-entity.js +87 -0
- package/dist-server/service/figure/figure-namespace-entity.js.map +1 -0
- package/dist-server/service/figure/figure-namespace-resolver.d.ts +5 -8
- package/dist-server/service/figure/figure-namespace-resolver.js +52 -36
- package/dist-server/service/figure/figure-namespace-resolver.js.map +1 -1
- package/dist-server/service/figure/figure-namespace.d.ts +28 -28
- package/dist-server/service/figure/figure-namespace.js +105 -63
- package/dist-server/service/figure/figure-namespace.js.map +1 -1
- package/dist-server/service/figure/figure-package-export.js +4 -2
- package/dist-server/service/figure/figure-package-export.js.map +1 -1
- package/dist-server/service/figure/figure-public.d.ts +19 -0
- package/dist-server/service/figure/figure-public.js +54 -0
- package/dist-server/service/figure/figure-public.js.map +1 -0
- package/dist-server/service/figure/figure-query.d.ts +7 -0
- package/dist-server/service/figure/figure-query.js +59 -5
- package/dist-server/service/figure/figure-query.js.map +1 -1
- package/dist-server/service/figure/figure-rename.d.ts +25 -2
- package/dist-server/service/figure/figure-rename.js +21 -2
- package/dist-server/service/figure/figure-rename.js.map +1 -1
- package/dist-server/service/figure/figure-tools.js +1 -1
- package/dist-server/service/figure/figure-tools.js.map +1 -1
- package/dist-server/service/figure/figure-type.d.ts +1 -0
- package/dist-server/service/figure/figure-type.js +4 -0
- package/dist-server/service/figure/figure-type.js.map +1 -1
- package/dist-server/service/figure/figure.d.ts +1 -0
- package/dist-server/service/figure/figure.js +5 -0
- package/dist-server/service/figure/figure.js.map +1 -1
- package/dist-server/service/figure/index.d.ts +2 -1
- package/dist-server/service/figure/index.js +2 -1
- package/dist-server/service/figure/index.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/server/index.ts +2 -0
- package/server/routers/figure-mesh-router.ts +5 -1
- package/server/routers/figure-thumbnail-router.ts +3 -5
- package/server/service/figure/figure-categories.ts +50 -0
- package/server/service/figure/figure-category-db.test.ts +127 -0
- package/server/service/figure/figure-deprecation-db.test.ts +110 -6
- package/server/service/figure/figure-format.ts +43 -1
- package/server/service/figure/figure-mesh-db.test.ts +4 -2
- package/server/service/figure/figure-mutation.ts +79 -37
- package/server/service/figure/figure-namespace-db.test.ts +140 -74
- package/server/service/figure/figure-namespace-entity.ts +67 -0
- package/server/service/figure/figure-namespace-resolver.ts +39 -26
- package/server/service/figure/figure-namespace.ts +122 -81
- package/server/service/figure/figure-package-export.ts +6 -3
- package/server/service/figure/figure-proposal-db.test.ts +5 -3
- package/server/service/figure/figure-public-db.test.ts +180 -0
- package/server/service/figure/figure-public.ts +60 -0
- package/server/service/figure/figure-query.ts +48 -5
- package/server/service/figure/figure-release-check.test.ts +63 -0
- package/server/service/figure/figure-release-record-db.test.ts +7 -5
- package/server/service/figure/figure-rename-db.test.ts +50 -4
- package/server/service/figure/figure-rename.ts +37 -3
- package/server/service/figure/figure-tools.ts +1 -1
- package/server/service/figure/figure-type.ts +3 -0
- package/server/service/figure/figure.ts +8 -0
- package/server/service/figure/index.ts +2 -1
- package/translations/en.json +12 -3
- package/translations/es.json +12 -3
- package/translations/ja.json +12 -3
- package/translations/ko.json +12 -3
- package/translations/ms.json +12 -3
- package/translations/zh.json +12 -3
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { In, Like, Not } from 'typeorm'
|
|
2
|
+
|
|
3
|
+
import { getRepository } from '@things-factory/shell'
|
|
4
|
+
|
|
5
|
+
import { Figure } from './figure.js'
|
|
6
|
+
import { FigureHistory } from './figure-history.js'
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Public figures (LV figure-server design G2): a figure its domain made public is read by every domain -- its releases,
|
|
10
|
+
* its picture, the files its releases draw from -- and drawn by its full name {namespace}/{type}, which points at one
|
|
11
|
+
* figure on the server because a namespace belongs to one domain (G1). Its draft stays its domain's. Writing it stays
|
|
12
|
+
* its domain's (G3). Each reader asks its own domain and its parent first; a public figure is the last place looked.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type Reader = { id: string; parentId?: string | null }
|
|
16
|
+
|
|
17
|
+
/** The domains a reader reads as its own: itself and its parent. */
|
|
18
|
+
export const ownScope = (domain: Reader) => In([domain.id, domain.parentId].filter(Boolean) as string[])
|
|
19
|
+
|
|
20
|
+
/** A figure the reader may read the releases of: its own or its parent's, or any domain's public one. */
|
|
21
|
+
export async function readableFigure(domain: Reader, id: string, select?: Record<string, unknown>): Promise<Figure | null> {
|
|
22
|
+
return getRepository(Figure).findOne({
|
|
23
|
+
where: [
|
|
24
|
+
{ domain: { id: ownScope(domain) }, id },
|
|
25
|
+
{ public: true, id }
|
|
26
|
+
],
|
|
27
|
+
relations: { domain: true },
|
|
28
|
+
...(select ? { select } : {})
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether the figure is another domain's public one: the reader sees its releases, never its draft. */
|
|
33
|
+
export function foreignPublic(domain: Reader, figure: Figure): boolean {
|
|
34
|
+
const owner = figure.domain?.id ?? figure.domainId
|
|
35
|
+
return !!figure.public && owner !== domain.id && owner !== domain.parentId
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The public figure of a full name, in whatever domain owns its namespace. */
|
|
39
|
+
export async function publicFigureOfName(namespace: string, type: string): Promise<Figure | null> {
|
|
40
|
+
return getRepository(Figure).findOne({ where: { namespace, type, public: true }, relations: { domain: true } })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The domains whose kept file this is, among the public figures whose releases draw from it -- the places a reader may
|
|
45
|
+
* be given that file from beyond its own domain and its parent.
|
|
46
|
+
*/
|
|
47
|
+
export async function publicDomainsOfFile(hash: string, reader: Reader): Promise<string[]> {
|
|
48
|
+
const figures = await getRepository(Figure).find({
|
|
49
|
+
where: { public: true, domain: { id: Not(ownScope(reader)) } },
|
|
50
|
+
relations: { domain: true },
|
|
51
|
+
select: { id: true, domain: { id: true } }
|
|
52
|
+
})
|
|
53
|
+
if (!figures.length) return []
|
|
54
|
+
const histories = await getRepository(FigureHistory).find({
|
|
55
|
+
where: { originalId: In(figures.map(figure => figure.id)), state: 'released', source: Like(`%${hash}%`) },
|
|
56
|
+
select: { originalId: true }
|
|
57
|
+
})
|
|
58
|
+
const drawing = new Set(histories.map(history => history.originalId))
|
|
59
|
+
return [...new Set(figures.filter(figure => drawing.has(figure.id)).map(figure => figure.domain!.id))]
|
|
60
|
+
}
|
|
@@ -21,6 +21,8 @@ import { FigureReplacedDraft } from './figure-replaced-draft.js'
|
|
|
21
21
|
import { replacedDraftsOf } from './figure-revert.js'
|
|
22
22
|
import { proposalOutcomeOf } from './figure-proposal.js'
|
|
23
23
|
import { meshSummaryOf } from './figure-mesh-store.js'
|
|
24
|
+
import { readableFigure, publicFigureOfName } from './figure-public.js'
|
|
25
|
+
import { figureCategories } from './figure-categories.js'
|
|
24
26
|
|
|
25
27
|
@Resolver(Figure)
|
|
26
28
|
@UseMiddleware(SpeakRefusals)
|
|
@@ -61,6 +63,28 @@ export class FigureQuery {
|
|
|
61
63
|
return { items, total }
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Other domains' public figures (LV figure-server design G2): what this domain may read, package and draw besides its
|
|
68
|
+
* own and its parent's. The rows carry their domain, so a screen says whose each is. Their drafts are not opened.
|
|
69
|
+
*/
|
|
70
|
+
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
71
|
+
@Query(returns => FigureList, { description: "Other domains' public figures. Read only: their releases, never their drafts." })
|
|
72
|
+
async publicFigures(@Args() params: ListParam, @Ctx() context: ResolverContext): Promise<FigureList> {
|
|
73
|
+
const { domain } = context.state
|
|
74
|
+
const mine = [domain.id, domain.parentId].filter(Boolean)
|
|
75
|
+
const queryBuilder = getQueryBuilderFromListParams({
|
|
76
|
+
repository: getRepository(Figure),
|
|
77
|
+
params,
|
|
78
|
+
alias: 'figure',
|
|
79
|
+
searchables: ['type', 'name', 'description', 'category', 'tags']
|
|
80
|
+
})
|
|
81
|
+
.leftJoinAndSelect('figure.domain', 'owner')
|
|
82
|
+
.andWhere('figure.public = :public', { public: true })
|
|
83
|
+
.andWhere('owner.id NOT IN (:...mine)', { mine })
|
|
84
|
+
const [items, total] = await queryBuilder.getManyAndCount()
|
|
85
|
+
return { items, total }
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
/**
|
|
65
89
|
* 등록된 타입 이름 목록.
|
|
66
90
|
*
|
|
@@ -80,8 +104,10 @@ export class FigureQuery {
|
|
|
80
104
|
async figureVersions(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory[]> {
|
|
81
105
|
const { domain } = context.state
|
|
82
106
|
|
|
107
|
+
/* Its own, its parent's, or another domain's public figure (G2): releases are what a public figure shows. */
|
|
108
|
+
if (!(await readableFigure(domain, id, { id: true, public: true, domain: { id: true } }))) return []
|
|
83
109
|
return await getRepository(FigureHistory).find({
|
|
84
|
-
where: {
|
|
110
|
+
where: { originalId: id },
|
|
85
111
|
relations: ['updater'],
|
|
86
112
|
order: { version: 'DESC' },
|
|
87
113
|
take: 10
|
|
@@ -125,8 +151,9 @@ export class FigureQuery {
|
|
|
125
151
|
async figureReleased(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<FigureHistory> {
|
|
126
152
|
const { domain } = context.state
|
|
127
153
|
|
|
154
|
+
if (!(await readableFigure(domain, id, { id: true, public: true, domain: { id: true } }))) return null
|
|
128
155
|
return await getRepository(FigureHistory).findOne({
|
|
129
|
-
where: {
|
|
156
|
+
where: { originalId: id },
|
|
130
157
|
order: { version: 'DESC' }
|
|
131
158
|
})
|
|
132
159
|
}
|
|
@@ -168,7 +195,7 @@ export class FigureQuery {
|
|
|
168
195
|
.limit(1)
|
|
169
196
|
.getOne()
|
|
170
197
|
|
|
171
|
-
return inspectSource(figure.source, released?.source, context.t, figure.type)
|
|
198
|
+
return inspectSource(figure.source, released?.source, context.t, figure.type, figure.category)
|
|
172
199
|
}
|
|
173
200
|
|
|
174
201
|
/**
|
|
@@ -202,7 +229,15 @@ export class FigureQuery {
|
|
|
202
229
|
.limit(1)
|
|
203
230
|
.getOne()
|
|
204
231
|
|
|
205
|
-
return inspectSource(source, released?.source, context.t, figure.type)
|
|
232
|
+
return inspectSource(source, released?.source, context.t, figure.type, figure.category)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The categories this server allows (G5), or null when a category is free text: the modeller offers them to choose from. */
|
|
236
|
+
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
237
|
+
@Query(returns => [String], { nullable: true, description: 'The categories this server allows, or null when a category is free text.' })
|
|
238
|
+
async figureCategories(): Promise<string[] | null> {
|
|
239
|
+
const codes = figureCategories()
|
|
240
|
+
return codes ? [...codes] : null
|
|
206
241
|
}
|
|
207
242
|
|
|
208
243
|
@Directive('@privilege(category: "figure", privilege: "query", domainOwnerGranted: true)')
|
|
@@ -357,7 +392,9 @@ export class FigureQuery {
|
|
|
357
392
|
continue
|
|
358
393
|
}
|
|
359
394
|
const found =
|
|
360
|
-
(await releasedSourceOfName(domain.id, name)) ??
|
|
395
|
+
(await releasedSourceOfName(domain.id, name)) ??
|
|
396
|
+
(domain.parentId ? await releasedSourceOfName(domain.parentId, name) : null) ??
|
|
397
|
+
(await publicReleasedSourceOf(name))
|
|
361
398
|
if (!found) {
|
|
362
399
|
refuse(type, new Refusal('figure-not-released', { type }, 'no release is published here.'))
|
|
363
400
|
continue
|
|
@@ -388,6 +425,12 @@ async function releasedSourceOfName(domainId: string, name: FigureName) {
|
|
|
388
425
|
return found && found.figure.namespace === name.namespace ? found : null
|
|
389
426
|
}
|
|
390
427
|
|
|
428
|
+
/** The released source of another domain's public figure by full name (G2): looked at after the reader's own. */
|
|
429
|
+
async function publicReleasedSourceOf(name: FigureName) {
|
|
430
|
+
const figure = await publicFigureOfName(name.namespace, name.type)
|
|
431
|
+
return figure?.domain ? releasedSourceOfName(figure.domain.id, name) : null
|
|
432
|
+
}
|
|
433
|
+
|
|
391
434
|
/** One numbered release of a figure, or null when it has none by that number. */
|
|
392
435
|
async function releaseNumbered(figure: Figure, version: number): Promise<{ figure: Figure; source: string; version: number } | null> {
|
|
393
436
|
const row = await getRepository(FigureHistory).findOne({
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release checks an app adds (LV figure-server design G10): they see the asset, the row's type and category and the
|
|
3
|
+
* reader's words, and their findings join the gate's -- a blocking one blocks, a said one does not; a check that
|
|
4
|
+
* throws blocks by name rather than being skipped; one taken off is gone.
|
|
5
|
+
*/
|
|
6
|
+
import { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy } from '@hatiolab/figure-model'
|
|
7
|
+
|
|
8
|
+
import { inspectSource, registerFigureReleaseCheck } from './figure-format'
|
|
9
|
+
|
|
10
|
+
const source = () => {
|
|
11
|
+
let asset = addFigureBox(createFigureAsset('Mixer'), { id: 'body', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
12
|
+
asset = applyFigureAuthoring(asset, { kind: 'attach', part: 'body', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
13
|
+
return serializeFigureAsset(applyFigureAuthoring(asset, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(asset, { over: 'range' }), placement: 'floor' }))
|
|
14
|
+
}
|
|
15
|
+
const codes = (inspection: { findings: { code: string; blocking: boolean }[] }) => inspection.findings.map(f => [f.code, f.blocking])
|
|
16
|
+
|
|
17
|
+
describe('release checks an app adds', () => {
|
|
18
|
+
const off: Array<() => void> = []
|
|
19
|
+
afterEach(() => off.splice(0).forEach(take => take()))
|
|
20
|
+
|
|
21
|
+
it('none: the gate as it was', () => {
|
|
22
|
+
const inspection = inspectSource(source(), null, undefined, 'mixer', 'EQUIPMENT')
|
|
23
|
+
expect(inspection.blocked).toBe(false)
|
|
24
|
+
expect(inspection.findings.filter(f => f.code.startsWith('app-'))).toEqual([])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('see the asset, the type and the category; a blocking finding blocks, a said one does not', () => {
|
|
28
|
+
const seen: unknown[] = []
|
|
29
|
+
off.push(
|
|
30
|
+
registerFigureReleaseCheck(input => {
|
|
31
|
+
seen.push([input.asset.version, input.type, input.category])
|
|
32
|
+
return input.category === 'CARRIER'
|
|
33
|
+
? [{ code: 'app-carrier-one-part', message: 'a carrier is one part', blocking: false }]
|
|
34
|
+
: [{ code: 'app-origin-off', message: 'the origin is off the middle', blocking: true }]
|
|
35
|
+
})
|
|
36
|
+
)
|
|
37
|
+
const equipment = inspectSource(source(), null, undefined, 'mixer', 'EQUIPMENT')
|
|
38
|
+
expect(codes(equipment)).toContainEqual(['app-origin-off', true])
|
|
39
|
+
expect(equipment.blocked).toBe(true)
|
|
40
|
+
const carrier = inspectSource(source(), null, undefined, 'roll', 'CARRIER')
|
|
41
|
+
expect(codes(carrier)).toContainEqual(['app-carrier-one-part', false])
|
|
42
|
+
expect(carrier.blocked).toBe(false)
|
|
43
|
+
expect(seen).toEqual([['figure-asset-1', 'mixer', 'EQUIPMENT'], ['figure-asset-1', 'roll', 'CARRIER']])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('a check that throws blocks by name; one taken off is gone', () => {
|
|
47
|
+
const take = registerFigureReleaseCheck(() => {
|
|
48
|
+
throw new Error('no budget table')
|
|
49
|
+
})
|
|
50
|
+
const inspection = inspectSource(source(), null, undefined, 'mixer', 'EQUIPMENT')
|
|
51
|
+
expect(codes(inspection)).toContainEqual(['release-check-failed', true])
|
|
52
|
+
expect(inspection.findings.find(f => f.code === 'release-check-failed')?.message).toMatch(/no budget table/)
|
|
53
|
+
take()
|
|
54
|
+
expect(inspectSource(source(), null, undefined, 'mixer', 'EQUIPMENT').blocked).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('a source the kernel refuses is not shown to them', () => {
|
|
58
|
+
let called = false
|
|
59
|
+
off.push(registerFigureReleaseCheck(() => ((called = true), [])))
|
|
60
|
+
expect(inspectSource('{"version":"figure-asset-1"}', null, undefined, 'x', null).blocked).toBe(true)
|
|
61
|
+
expect(called).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -10,6 +10,7 @@ const authBase = require('@things-factory/auth-base')
|
|
|
10
10
|
const settingBase = require('@things-factory/setting-base')
|
|
11
11
|
const { createFigureAsset, addFigureBox, serializeFigureAsset, applyFigureAuthoring, proposeFigureOccupancy, palletizerRobotFigure, FIGURE_KERNEL_VERSION } = require('@hatiolab/figure-model')
|
|
12
12
|
|
|
13
|
+
let FigureNamespace: any
|
|
13
14
|
let dataSource: DataSource | null = null
|
|
14
15
|
let Figure: any
|
|
15
16
|
let FigureHistory: any
|
|
@@ -28,6 +29,7 @@ function loadCompiled(modulePath: string): any {
|
|
|
28
29
|
|
|
29
30
|
beforeAll(async () => {
|
|
30
31
|
Figure = loadCompiled('service/figure/figure').Figure
|
|
32
|
+
FigureNamespace = loadCompiled('service/figure/figure-namespace-entity').FigureNamespace
|
|
31
33
|
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
32
34
|
Subscriber = loadCompiled('service/figure/event-subscriber').FigureHistoryEntitySubscriber
|
|
33
35
|
FigureMutation = loadCompiled('service/figure/figure-mutation').FigureMutation
|
|
@@ -38,7 +40,7 @@ beforeAll(async () => {
|
|
|
38
40
|
synchronize: true,
|
|
39
41
|
dropSchema: true,
|
|
40
42
|
namingStrategy: new shell.NamingStrategy(),
|
|
41
|
-
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory],
|
|
43
|
+
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory, FigureNamespace],
|
|
42
44
|
subscribers: [Subscriber],
|
|
43
45
|
logging: false
|
|
44
46
|
})
|
|
@@ -50,9 +52,9 @@ afterAll(async () => {
|
|
|
50
52
|
dataSource = null
|
|
51
53
|
})
|
|
52
54
|
|
|
53
|
-
/** The domain
|
|
55
|
+
/** The domain's own default namespace, its subdomain, that every figure here is created and released under (G1). */
|
|
54
56
|
async function namespaced(domain: any) {
|
|
55
|
-
await dataSource!.getRepository(
|
|
57
|
+
await dataSource!.getRepository(FigureNamespace).save({ domain, name: domain.subdomain, state: 'active', isDefault: true })
|
|
56
58
|
return domain
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -94,7 +96,7 @@ describe('releaseFigure records it', () => {
|
|
|
94
96
|
const domain = await namespaced(await dataSource!.getRepository(shell.Domain).save({ name: 'release-door', subdomain: 'release-door' }))
|
|
95
97
|
const tx = dataSource!.manager
|
|
96
98
|
const context = { state: { domain, user: undefined, tx } }
|
|
97
|
-
const figure = await dataSource!.getRepository(Figure).save({ namespace:
|
|
99
|
+
const figure = await dataSource!.getRepository(Figure).save({ namespace: domain.subdomain, type: 'RELEASE_DOOR', name: 'door', source: releasable(100), state: 'draft', version: 0, domain })
|
|
98
100
|
await new FigureMutation().releaseFigure(figure.id, 'one', context)
|
|
99
101
|
await dataSource!.getRepository(Figure).save({ ...(await dataSource!.getRepository(Figure).findOneOrFail({ where: { id: figure.id } })), domain, source: releasable(300), state: 'draft' })
|
|
100
102
|
await new FigureMutation().releaseFigure(figure.id, 'two', context)
|
|
@@ -125,7 +127,7 @@ describe('a release of a figure that moves keeps its joint list', () => {
|
|
|
125
127
|
it('schema 2, the wrist following -(j2 + j3), controls with both units; another axis is refused', async () => {
|
|
126
128
|
const domain = await namespaced(await dataSource!.getRepository(shell.Domain).save({ name: 'release-joints', subdomain: 'release-joints' }))
|
|
127
129
|
const context = { state: { domain, user: undefined, tx: dataSource!.manager } }
|
|
128
|
-
const figure = await dataSource!.getRepository(Figure).save({ namespace:
|
|
130
|
+
const figure = await dataSource!.getRepository(Figure).save({ namespace: domain.subdomain, type: 'PALLETIZER', name: 'palletizer', source: palletizer(), state: 'draft', version: 0, domain })
|
|
129
131
|
await new FigureMutation().releaseFigure(figure.id, 'one', context)
|
|
130
132
|
|
|
131
133
|
const [row] = await dataSource!.getRepository(FigureHistory).find({ where: { originalId: figure.id } })
|
|
@@ -15,6 +15,7 @@ const shell = require('@things-factory/shell')
|
|
|
15
15
|
const authBase = require('@things-factory/auth-base')
|
|
16
16
|
const settingBase = require('@things-factory/setting-base')
|
|
17
17
|
|
|
18
|
+
let FigureNamespace: any
|
|
18
19
|
let dataSource: DataSource | null = null
|
|
19
20
|
let Figure: any
|
|
20
21
|
let FigureHistory: any
|
|
@@ -38,6 +39,7 @@ function loadCompiled(modulePath: string): any {
|
|
|
38
39
|
|
|
39
40
|
beforeAll(async () => {
|
|
40
41
|
Figure = loadCompiled('service/figure/figure').Figure
|
|
42
|
+
FigureNamespace = loadCompiled('service/figure/figure-namespace-entity').FigureNamespace
|
|
41
43
|
FigureHistory = loadCompiled('service/figure/figure-history').FigureHistory
|
|
42
44
|
renameFacts = loadCompiled('service/figure/figure-rename').renameFacts
|
|
43
45
|
|
|
@@ -47,7 +49,7 @@ beforeAll(async () => {
|
|
|
47
49
|
synchronize: true,
|
|
48
50
|
dropSchema: true,
|
|
49
51
|
namingStrategy: new shell.NamingStrategy(),
|
|
50
|
-
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory],
|
|
52
|
+
entities: [...shell.entities, ...authBase.entities, ...settingBase.entities, Figure, FigureHistory, FigureNamespace],
|
|
51
53
|
logging: false
|
|
52
54
|
})
|
|
53
55
|
|
|
@@ -69,9 +71,9 @@ afterAll(async () => {
|
|
|
69
71
|
dataSource = null
|
|
70
72
|
})
|
|
71
73
|
|
|
72
|
-
/** The domain
|
|
74
|
+
/** The domain's own default namespace, its subdomain, that every figure here is created and released under (G1). */
|
|
73
75
|
async function namespaced(domain: any) {
|
|
74
|
-
await dataSource!.getRepository(
|
|
76
|
+
await dataSource!.getRepository(FigureNamespace).save({ domain, name: domain.subdomain, state: 'active', isDefault: true })
|
|
75
77
|
return domain
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -108,7 +110,7 @@ describe('이름을 바꾸기 전에 읽는 사실', () => {
|
|
|
108
110
|
it('★ 판본 질의가 실제로 돈다 — 칸 이름이 틀리면 여기서 던진다', async () => {
|
|
109
111
|
const figure = await seedFigure()
|
|
110
112
|
|
|
111
|
-
expect(await facts(figure, 'robot-arm')).toEqual({ version: 0, hasHistory: false, takenByAnother: false })
|
|
113
|
+
expect(await facts(figure, 'robot-arm')).toEqual({ version: 0, hasHistory: false, takenByAnother: false, releasedBefore: false })
|
|
112
114
|
})
|
|
113
115
|
|
|
114
116
|
it('★ 판본이 있으면 그것을 읽어 온다', async () => {
|
|
@@ -189,3 +191,47 @@ describe('처음 저장에 타입 이름을 받는다', () => {
|
|
|
189
191
|
expect(model.isPlaceholderType(saved.type)).toBe(true)
|
|
190
192
|
})
|
|
191
193
|
})
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* A type released and then deleted before deleting a released figure was refused (LV figure-server design G4): taking that
|
|
197
|
+
* name again would number from 1, and one reference `ns/type@n` would name two contents.
|
|
198
|
+
*/
|
|
199
|
+
describe('발행한 뒤 지운 이름은 다시 쓰지 않는다', () => {
|
|
200
|
+
let FigureMutation: any
|
|
201
|
+
let FIGURE_SOURCE: string
|
|
202
|
+
beforeAll(() => {
|
|
203
|
+
FigureMutation = loadCompiled('service/figure/figure-mutation').FigureMutation
|
|
204
|
+
const model = require('@hatiolab/figure-model')
|
|
205
|
+
FIGURE_SOURCE = model.serializeFigureAsset(
|
|
206
|
+
model.addFigureBox(model.createFigureAsset('상자'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
const releasedThenDeleted = async (type: string) => {
|
|
210
|
+
const figure = await seedFigure({ type, name: type, version: 1, state: 'released' })
|
|
211
|
+
await dataSource!.getRepository(FigureHistory).save({ originalId: figure.id, version: 1, type, name: type, source: '{}', domain: DOMAIN })
|
|
212
|
+
await dataSource!.getRepository(Figure).softDelete({ id: figure.id })
|
|
213
|
+
return figure
|
|
214
|
+
}
|
|
215
|
+
const context = () => ({ state: { domain: DOMAIN, user: undefined, tx: dataSource!.manager } })
|
|
216
|
+
|
|
217
|
+
it('사실: 발행한 뒤 지운 같은 이름이 있으면 releasedBefore, 발행 없이 지운 것은 아니다', async () => {
|
|
218
|
+
await releasedThenDeleted('shipped-gone')
|
|
219
|
+
const draftGone = await seedFigure({ type: 'draft-gone', name: 'draft-gone' })
|
|
220
|
+
await dataSource!.getRepository(Figure).softDelete({ id: draftGone.id })
|
|
221
|
+
const fresh = await seedFigure({ type: 'fresh-one', name: 'fresh-one' })
|
|
222
|
+
expect((await facts(fresh, 'shipped-gone')).releasedBefore).toBe(true)
|
|
223
|
+
expect((await facts(fresh, 'draft-gone')).releasedBefore).toBe(false)
|
|
224
|
+
expect((await facts(fresh, 'never-used')).releasedBefore).toBe(false)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('이름 바꾸기로도, 처음 저장으로도, 새로 만들기로도 그 이름을 받지 않는다', async () => {
|
|
228
|
+
await releasedThenDeleted('gone-mixer')
|
|
229
|
+
const draft = await seedFigure({ type: 'mixer-draft', name: 'mixer-draft' })
|
|
230
|
+
await expect(new FigureMutation().updateFigure(draft.id, { type: 'gone-mixer' }, context())).rejects.toMatchObject({ code: 'type-released-before' })
|
|
231
|
+
await expect(
|
|
232
|
+
new FigureMutation().saveFigure(FIGURE_SOURCE, context(), undefined, undefined, '7c3d4e5f-6a7b-4c8d-9e0f-a1b2c3d4e5f6', undefined, 'gone-mixer')
|
|
233
|
+
).rejects.toThrow(/발행한 뒤 지운/)
|
|
234
|
+
await expect(new FigureMutation().createFigure({ type: 'gone-mixer', name: 'again', source: FIGURE_SOURCE }, context())).rejects.toThrow(/발행한 뒤 지운/)
|
|
235
|
+
expect(await dataSource!.getRepository(Figure).count({ where: { type: 'gone-mixer' } })).toBe(0)
|
|
236
|
+
})
|
|
237
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { In, IsNull, Not } from 'typeorm'
|
|
1
2
|
import { TYPE_LENGTH, TYPE_PATTERN } from '@hatiolab/figure-model'
|
|
2
3
|
/**
|
|
3
4
|
* 초안의 타입을 고치는 규칙 — ADR-0063 결정 4.
|
|
@@ -29,11 +30,38 @@ export interface RenameFacts {
|
|
|
29
30
|
hasHistory: boolean
|
|
30
31
|
/** 같은 도메인에서 그 이름을 이미 쓰는 **다른** 도형이 있나. */
|
|
31
32
|
takenByAnother: boolean
|
|
33
|
+
/**
|
|
34
|
+
* 같은 도메인에서 그 이름으로 발행한 뒤 지워진 도형이 있나. 발행한 도형을 지울 수 없게 되기(G4) 전에 지워진 것이다.
|
|
35
|
+
* 그 이름을 다시 쓰면 판 번호가 1부터 다시 매겨져 같은 `ns/type@n` 이 다른 내용을 가리킨다.
|
|
36
|
+
*/
|
|
37
|
+
releasedBefore?: boolean
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/** Whether a figure of this type was released and then deleted in the domain (deleted before G4 refused it). */
|
|
41
|
+
export async function typeReleasedBefore(
|
|
42
|
+
repositories: {
|
|
43
|
+
figures: { find(options: any): Promise<{ id?: string }[]> }
|
|
44
|
+
histories: { count(options: any): Promise<number> }
|
|
45
|
+
},
|
|
46
|
+
domain: { id: string },
|
|
47
|
+
type: string
|
|
48
|
+
): Promise<boolean> {
|
|
49
|
+
const deleted = await repositories.figures.find({
|
|
50
|
+
where: { domain: { id: domain.id }, type, deletedAt: Not(IsNull()) },
|
|
51
|
+
withDeleted: true,
|
|
52
|
+
select: ['id']
|
|
53
|
+
})
|
|
54
|
+
const ids = deleted.map(figure => figure.id).filter(Boolean)
|
|
55
|
+
return ids.length > 0 && (await repositories.histories.count({ where: { originalId: In(ids) } })) > 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Said when a type was released and deleted before: a new figure may not take it. */
|
|
59
|
+
export const releasedBeforeMessage = (type: string) =>
|
|
60
|
+
`type 「${type}」 는 이 도메인에서 발행한 뒤 지운 도형의 이름이다 — 다시 쓰면 같은 판 번호가 다른 내용을 가리키므로 다른 이름을 고른다`
|
|
61
|
+
|
|
34
62
|
export class FigureRenameRefused extends Error {
|
|
35
63
|
constructor(
|
|
36
|
-
readonly code: 'empty-type' | 'malformed-type' | 'already-released' | 'type-taken',
|
|
64
|
+
readonly code: 'empty-type' | 'malformed-type' | 'already-released' | 'type-taken' | 'type-released-before',
|
|
37
65
|
message: string
|
|
38
66
|
) {
|
|
39
67
|
super(message)
|
|
@@ -80,6 +108,10 @@ export function renameDraftType(
|
|
|
80
108
|
throw new FigureRenameRefused('type-taken', `type 「${next}」 는 이 도메인에서 이미 쓰고 있다`)
|
|
81
109
|
}
|
|
82
110
|
|
|
111
|
+
if (facts.releasedBefore) {
|
|
112
|
+
throw new FigureRenameRefused('type-released-before', releasedBeforeMessage(next))
|
|
113
|
+
}
|
|
114
|
+
|
|
83
115
|
/* The asset carries no `type`: the name a board uses is the row's alone, so only the row changes. */
|
|
84
116
|
return { type: next, source: current.source }
|
|
85
117
|
}
|
|
@@ -94,7 +126,7 @@ export function renameDraftType(
|
|
|
94
126
|
*/
|
|
95
127
|
export async function renameFacts(
|
|
96
128
|
repositories: {
|
|
97
|
-
figures: { findOne(options: any): Promise<{ id?: string } | null> }
|
|
129
|
+
figures: { findOne(options: any): Promise<{ id?: string } | null>; find(options: any): Promise<{ id?: string }[]> }
|
|
98
130
|
histories: { count(options: any): Promise<number> }
|
|
99
131
|
},
|
|
100
132
|
figure: { id?: string; version?: number },
|
|
@@ -105,5 +137,7 @@ export async function renameFacts(
|
|
|
105
137
|
const hasHistory = (await repositories.histories.count({ where: { originalId: figure.id } })) > 0
|
|
106
138
|
const taken = next ? await repositories.figures.findOne({ where: { domain: { id: domain.id }, type: next } }) : null
|
|
107
139
|
|
|
108
|
-
|
|
140
|
+
const releasedBefore = next ? await typeReleasedBefore(repositories, domain, next) : false
|
|
141
|
+
|
|
142
|
+
return { version: figure.version, hasHistory, takenByAnother: !!taken && taken.id !== figure.id, releasedBefore }
|
|
109
143
|
}
|
|
@@ -415,7 +415,7 @@ function figureToolSpecs(): ToolSpec[] {
|
|
|
415
415
|
const current = sourceForTool(row, ctx?.host)
|
|
416
416
|
const { inspectSource } = getDeps()
|
|
417
417
|
/* Inspected whole, here; only what the model is told is held to its budget. */
|
|
418
|
-
return { ...findingsWithinBudget(inspectSource(current.source ? JSON.stringify(current.source) : null, null, undefined, row.type)), draft: current.draft }
|
|
418
|
+
return { ...findingsWithinBudget(inspectSource(current.source ? JSON.stringify(current.source) : null, null, undefined, row.type, row.category)), draft: current.draft }
|
|
419
419
|
}
|
|
420
420
|
},
|
|
421
421
|
{
|
|
@@ -38,6 +38,9 @@ export class NewFigure {
|
|
|
38
38
|
|
|
39
39
|
@Field({ nullable: true, description: 'A base64 encoded thumbnail image.' })
|
|
40
40
|
thumbnail?: string
|
|
41
|
+
|
|
42
|
+
@Field({ nullable: true, description: "The domain's namespace to create it under; the domain's default when none." })
|
|
43
|
+
namespace?: string
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
/**
|
|
@@ -113,6 +113,14 @@ export class Figure {
|
|
|
113
113
|
@Field({ nullable: true, description: 'The type to use instead of a retired figure.' })
|
|
114
114
|
supersededBy?: string
|
|
115
115
|
|
|
116
|
+
/*
|
|
117
|
+
공개(LV figure-server design G2). 다른 도메인도 이 figure 의 **발행본**을 읽고 꾸러미에 담고 도면에서 정식 이름으로
|
|
118
|
+
부른다. 초안은 소유 도메인만 본다. 고치고 발행하는 것은 소유 도메인뿐이다(G3). `setFigurePublic` 이 정한다.
|
|
119
|
+
*/
|
|
120
|
+
@Column({ nullable: true, default: false })
|
|
121
|
+
@Field({ nullable: true, description: 'Public: other domains read its releases, put them in packages and draw them by full name. Only its domain changes it.' })
|
|
122
|
+
public?: boolean
|
|
123
|
+
|
|
116
124
|
/**
|
|
117
125
|
* 분류 하나. **팔레트에서 묶어 보이는 기준이다.**
|
|
118
126
|
*
|
|
@@ -2,11 +2,12 @@ import { Figure } from './figure.js'
|
|
|
2
2
|
import { FigureHistory } from './figure-history.js'
|
|
3
3
|
import { FigureReplacedDraft } from './figure-replaced-draft.js'
|
|
4
4
|
import { FigureProposalRecord } from './figure-proposal-record.js'
|
|
5
|
+
import { FigureNamespace } from './figure-namespace-entity.js'
|
|
5
6
|
import { FigureQuery } from './figure-query.js'
|
|
6
7
|
import { FigureMutation } from './figure-mutation.js'
|
|
7
8
|
import { FigureNamespaceResolver } from './figure-namespace-resolver.js'
|
|
8
9
|
import { FigureHistoryEntitySubscriber } from './event-subscriber.js'
|
|
9
10
|
|
|
10
|
-
export const entities = [Figure, FigureHistory, FigureReplacedDraft, FigureProposalRecord]
|
|
11
|
+
export const entities = [Figure, FigureHistory, FigureReplacedDraft, FigureProposalRecord, FigureNamespace]
|
|
11
12
|
export const resolvers = [FigureQuery, FigureMutation, FigureNamespaceResolver]
|
|
12
13
|
export const subscribers = [FigureHistoryEntitySubscriber]
|
package/translations/en.json
CHANGED
|
@@ -97,8 +97,7 @@
|
|
|
97
97
|
"figure.gate.origin-off-centre.message": "The origin is off the middle of the declared room: {off}.",
|
|
98
98
|
"figure.gate.origin-off-centre.why": "A drawing that places figures by the middle of their room puts this one that far from where it is drawn.",
|
|
99
99
|
"figure.gate.origin-off-centre.how": "Keep it when the figure is fastened there, as a door at its hinge. Move the origin to the middle to place it by its room.",
|
|
100
|
-
"error.figure-namespace-missing": "this domain has no
|
|
101
|
-
"error.figure-namespace-changed": "the figure namespace of this domain is {namespace} and cannot change. set {setting} back to {namespace}.",
|
|
100
|
+
"error.figure-namespace-missing": "this domain has no default figure namespace. add one first.",
|
|
102
101
|
"error.figure-namespace-invalid": "{value} is not a figure namespace. use lower-case letters, digits, dots and hyphens, for example hatiolab.com.",
|
|
103
102
|
"error.figure-type-malformed": "{type} is not a figure type. use lower-case letters, digits and single hyphens, {min} to {max} characters, starting with a letter.",
|
|
104
103
|
"error.figure-name-malformed": "{type} is not a full figure name of the form namespace/type.",
|
|
@@ -152,5 +151,15 @@
|
|
|
152
151
|
"figure.gate.contract.slot-anchor-moved.why": "Every slot on it, and what sits there, stands on the new face after this release.",
|
|
153
152
|
"figure.proposal.label-create": "Make {name}",
|
|
154
153
|
"figure.proposal.label-revise": "Revise {name}",
|
|
155
|
-
"figure.proposal.reason": "{steps} commands · grade {grade} · {triangles} triangles · made in {attempts} tries"
|
|
154
|
+
"figure.proposal.reason": "{steps} commands · grade {grade} · {triangles} triangles · made in {attempts} tries",
|
|
155
|
+
"error.figure-released-not-deletable": "{type} has been released and cannot be deleted.",
|
|
156
|
+
"error.figure-namespace-taken": "the figure namespace {namespace} is already taken on this server.",
|
|
157
|
+
"error.figure-namespace-not-owned": "this domain does not own the figure namespace {namespace}.",
|
|
158
|
+
"error.figure-namespace-suspended": "the figure namespace {namespace} is suspended.",
|
|
159
|
+
"error.figure-namespace-default-suspended": "the default namespace {namespace} cannot be suspended. make another the default first.",
|
|
160
|
+
"error.figure-namespace-state": "{state} is not a namespace state. use active or suspended.",
|
|
161
|
+
"error.figure-public-unreleased": "{type} has no release yet: only released figures are made public.",
|
|
162
|
+
"error.figure-category-fixed": "the category {category} is set once and does not change.",
|
|
163
|
+
"error.figure-category-unknown": "{category} is not a category here. use one of {allowed}.",
|
|
164
|
+
"error.figure-category-missing": "{type} has no category. choose one of {allowed} before releasing."
|
|
156
165
|
}
|
package/translations/es.json
CHANGED
|
@@ -97,8 +97,7 @@
|
|
|
97
97
|
"figure.gate.origin-off-centre.message": "El origen está desplazado del centro del espacio declarado: {off}.",
|
|
98
98
|
"figure.gate.origin-off-centre.why": "Un plano que coloca las figuras por el centro de su espacio sitúa esta a esa distancia de donde está dibujada.",
|
|
99
99
|
"figure.gate.origin-off-centre.how": "Manténgalo si la figura se fija en ese punto, como una puerta por su bisagra. Mueva el origen al centro para colocarla por su espacio.",
|
|
100
|
-
"error.figure-namespace-missing": "este dominio no tiene un namespace de figura
|
|
101
|
-
"error.figure-namespace-changed": "el namespace de figura de este dominio es {namespace} y no se puede cambiar. Restablezca {setting} a {namespace}.",
|
|
100
|
+
"error.figure-namespace-missing": "este dominio no tiene un namespace de figura predeterminado. agregue uno primero.",
|
|
102
101
|
"error.figure-namespace-invalid": "{value} no es un namespace de Figure. Use minúsculas, dígitos, puntos y guiones, por ejemplo hatiolab.com.",
|
|
103
102
|
"error.figure-type-malformed": "{type} no es un tipo de figura. Use letras minúsculas, dígitos y guiones simples, de {min} a {max} caracteres, empezando por una letra.",
|
|
104
103
|
"error.figure-name-malformed": "{type} no es un nombre completo de figura con el formato namespace/type.",
|
|
@@ -152,5 +151,15 @@
|
|
|
152
151
|
"figure.gate.contract.slot-anchor-moved.why": "Cada ranura sobre él, y lo que contiene, queda en la nueva cara tras esta publicación.",
|
|
153
152
|
"figure.proposal.label-create": "Crear {name}",
|
|
154
153
|
"figure.proposal.label-revise": "Corregir {name}",
|
|
155
|
-
"figure.proposal.reason": "{steps} comandos · grado {grade} · {triangles} triángulos · hecho en {attempts} intentos"
|
|
154
|
+
"figure.proposal.reason": "{steps} comandos · grado {grade} · {triangles} triángulos · hecho en {attempts} intentos",
|
|
155
|
+
"error.figure-released-not-deletable": "{type} ya se publicó y no se puede eliminar.",
|
|
156
|
+
"error.figure-namespace-taken": "el namespace de figura {namespace} ya está en uso en este servidor.",
|
|
157
|
+
"error.figure-namespace-not-owned": "este dominio no posee el namespace de figura {namespace}.",
|
|
158
|
+
"error.figure-namespace-suspended": "el namespace de figura {namespace} está suspendido.",
|
|
159
|
+
"error.figure-namespace-default-suspended": "el namespace predeterminado {namespace} no se puede suspender. primero haga predeterminado otro.",
|
|
160
|
+
"error.figure-namespace-state": "{state} no es un estado de namespace. use active o suspended.",
|
|
161
|
+
"error.figure-public-unreleased": "{type} aún no se ha publicado: solo las figuras publicadas se pueden hacer públicas.",
|
|
162
|
+
"error.figure-category-fixed": "la categoría {category} se fija una vez y no cambia.",
|
|
163
|
+
"error.figure-category-unknown": "{category} no es una categoría aquí. use una de {allowed}.",
|
|
164
|
+
"error.figure-category-missing": "{type} no tiene categoría. elija una de {allowed} antes de publicar."
|
|
156
165
|
}
|
package/translations/ja.json
CHANGED
|
@@ -97,8 +97,7 @@
|
|
|
97
97
|
"figure.gate.origin-off-centre.message": "原点が宣言された空間の中央からずれています: {off}。",
|
|
98
98
|
"figure.gate.origin-off-centre.why": "空間の中央で Figure を配置する図面では、描画した位置からその分だけ離れて配置されます。",
|
|
99
99
|
"figure.gate.origin-off-centre.how": "ドアの蝶番のように、Figure をそこで取り付ける場合はそのままにしてください。空間の中央で配置するには、原点を中央に移動してください。",
|
|
100
|
-
"error.figure-namespace-missing": "
|
|
101
|
-
"error.figure-namespace-changed": "このドメインの Figure namespace は {namespace} で、変更できません。{setting} を {namespace} に戻してください。",
|
|
100
|
+
"error.figure-namespace-missing": "このドメインには既定の Figure namespace がありません。先に namespace を追加してください。",
|
|
102
101
|
"error.figure-namespace-invalid": "Figure namespace の形式ではありません({value})。英小文字・数字・ドット・ハイフンで入力してください。例: hatiolab.com",
|
|
103
102
|
"error.figure-type-malformed": "{type} は Figure type ではありません。英小文字で始め、英小文字・数字・単一のハイフンのみを使って {min}〜{max} 文字で指定してください。",
|
|
104
103
|
"error.figure-name-malformed": "{type} は namespace/type 形式の正式な Figure 名ではありません。",
|
|
@@ -152,5 +151,15 @@
|
|
|
152
151
|
"figure.gate.contract.slot-anchor-moved.why": "その上のすべてのスロットと置かれたものは、このリリース以降新しい面に立ちます。",
|
|
153
152
|
"figure.proposal.label-create": "{name} を作る",
|
|
154
153
|
"figure.proposal.label-revise": "{name} を直す",
|
|
155
|
-
"figure.proposal.reason": "コマンド {steps} 件 · 等級 {grade} · 三角形 {triangles} · {attempts} 回で作成"
|
|
154
|
+
"figure.proposal.reason": "コマンド {steps} 件 · 等級 {grade} · 三角形 {triangles} · {attempts} 回で作成",
|
|
155
|
+
"error.figure-released-not-deletable": "{type} は発行済みの Figure のため削除できません。",
|
|
156
|
+
"error.figure-namespace-taken": "Figure namespace {namespace} はこのサーバーで既に使われています。",
|
|
157
|
+
"error.figure-namespace-not-owned": "このドメインは Figure namespace {namespace} を所有していません。",
|
|
158
|
+
"error.figure-namespace-suspended": "Figure namespace {namespace} は停止中です。",
|
|
159
|
+
"error.figure-namespace-default-suspended": "既定の namespace {namespace} は停止できません。先に別の namespace を既定にしてください。",
|
|
160
|
+
"error.figure-namespace-state": "{state} は namespace の状態ではありません。active か suspended を使ってください。",
|
|
161
|
+
"error.figure-public-unreleased": "{type} はまだ発行されていません。公開できるのは発行済みの Figure だけです。",
|
|
162
|
+
"error.figure-category-fixed": "分類 {category} は一度決めたら変えません。",
|
|
163
|
+
"error.figure-category-unknown": "{category} はここで使う分類ではありません。{allowed} のいずれかを選んでください。",
|
|
164
|
+
"error.figure-category-missing": "{type} に分類がありません。発行する前に {allowed} のいずれかを選んでください。"
|
|
156
165
|
}
|
package/translations/ko.json
CHANGED
|
@@ -97,8 +97,7 @@
|
|
|
97
97
|
"figure.gate.origin-off-centre.message": "원점이 차지 공간 가운데에서 비켜 있습니다: {off}.",
|
|
98
98
|
"figure.gate.origin-off-centre.why": "공간 가운데로 도형을 놓는 도면에서는 그린 자리에서 그만큼 떨어져 놓입니다.",
|
|
99
99
|
"figure.gate.origin-off-centre.how": "문의 경첩처럼 붙이는 자리라면 그대로 둡니다. 공간 가운데로 놓이게 하려면 원점을 가운데로 옮깁니다.",
|
|
100
|
-
"error.figure-namespace-missing": "이
|
|
101
|
-
"error.figure-namespace-changed": "이 도메인의 Figure namespace 는 {namespace} 이며 바꿀 수 없습니다. 설정 {setting} 을 {namespace} 로 되돌려 주세요.",
|
|
100
|
+
"error.figure-namespace-missing": "이 도메인에는 기본 Figure namespace 가 없습니다. 먼저 namespace 를 추가해 주세요.",
|
|
102
101
|
"error.figure-namespace-invalid": "Figure namespace 형식이 아닙니다({value}). 영문 소문자·숫자·점·하이픈으로 입력해 주세요. 예: hatiolab.com",
|
|
103
102
|
"error.figure-type-malformed": "{type} 은 Figure type 이 아닙니다. 소문자로 시작하고 소문자·숫자·하이픈 하나씩만 써서 {min}~{max}자로 정해 주세요.",
|
|
104
103
|
"error.figure-name-malformed": "{type} 은 namespace/type 형식의 정식 이름이 아닙니다.",
|
|
@@ -152,5 +151,15 @@
|
|
|
152
151
|
"figure.gate.contract.slot-anchor-moved.why": "발행 후 해당 기준면의 모든 슬롯과 배치된 항목이 새 기준면으로 이동합니다.",
|
|
153
152
|
"figure.proposal.label-create": "{name} 만들기",
|
|
154
153
|
"figure.proposal.label-revise": "{name} 고치기",
|
|
155
|
-
"figure.proposal.reason": "명령 {steps}개 · 등급 {grade} · 삼각형 {triangles} · {attempts}번에 만들었다"
|
|
154
|
+
"figure.proposal.reason": "명령 {steps}개 · 등급 {grade} · 삼각형 {triangles} · {attempts}번에 만들었다",
|
|
155
|
+
"error.figure-released-not-deletable": "{type} 은 발행한 Figure 라 지울 수 없습니다.",
|
|
156
|
+
"error.figure-namespace-taken": "Figure namespace {namespace} 는 이 서버에서 이미 쓰고 있습니다.",
|
|
157
|
+
"error.figure-namespace-not-owned": "이 도메인은 Figure namespace {namespace} 를 갖고 있지 않습니다.",
|
|
158
|
+
"error.figure-namespace-suspended": "Figure namespace {namespace} 는 쉬는 중입니다.",
|
|
159
|
+
"error.figure-namespace-default-suspended": "기본 namespace {namespace} 는 쉬게 할 수 없습니다. 먼저 다른 namespace 를 기본으로 정해 주세요.",
|
|
160
|
+
"error.figure-namespace-state": "{state} 는 namespace 상태가 아닙니다. active 나 suspended 를 쓰세요.",
|
|
161
|
+
"error.figure-public-unreleased": "{type} 은 아직 발행하지 않았습니다. 발행한 Figure 만 공개할 수 있습니다.",
|
|
162
|
+
"error.figure-category-fixed": "분류 {category} 는 한 번 정하면 바꾸지 않습니다.",
|
|
163
|
+
"error.figure-category-unknown": "{category} 는 여기서 쓰는 분류가 아닙니다. {allowed} 가운데 하나를 고르세요.",
|
|
164
|
+
"error.figure-category-missing": "{type} 에 분류가 없습니다. 발행하기 전에 {allowed} 가운데 하나를 고르세요."
|
|
156
165
|
}
|