Skip to content

Commit 658a289

Browse files
fix(blocks): model extension menus and hat semantics
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 2952495 commit 658a289

7 files changed

Lines changed: 389 additions & 0 deletions

File tree

packages/hikkaku/src/blocks/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ export * from './pen'
99
export * from './procedures'
1010
export * from './sensing'
1111
export * from './sound'
12+
export * from './text2speech'
13+
export * from './translate'
14+
export * from './videosensing'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, test } from 'vite-plus/test'
2+
import { block, createBlocks } from '../core/composer'
3+
import { setLanguage, setVoice, speakAndWait } from './text2speech'
4+
5+
describe('blocks/text2speech', () => {
6+
test('creates text to speech extension blocks', () => {
7+
const blocks = createBlocks(() => {
8+
block('event_whenflagclicked', { topLevel: true })
9+
setVoice('TENOR')
10+
setLanguage('ja')
11+
speakAndWait('hello')
12+
})
13+
14+
const opcodes = Object.values(blocks).map((b) => b.opcode)
15+
expect(opcodes).toContain('text2speech_setVoice')
16+
expect(opcodes).toContain('text2speech_menu_voices')
17+
expect(opcodes).toContain('text2speech_setLanguage')
18+
expect(opcodes).toContain('text2speech_menu_languages')
19+
expect(opcodes).toContain('text2speech_speakAndWait')
20+
})
21+
})
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { InputType } from 'sb3-types/enum'
2+
import { fromPrimitiveSource, menuInput } from '../core/block-helper'
3+
import { block, valueBlock } from '../core/composer'
4+
import type { HikkakuString, PrimitiveSource } from '../core/types'
5+
6+
export type TextToSpeechVoice =
7+
| 'ALTO'
8+
| 'TENOR'
9+
| 'SQUEAK'
10+
| 'GIANT'
11+
| 'KITTEN'
12+
| 'GOOGLE'
13+
14+
/**
15+
* Speaks text and waits until completion.
16+
*
17+
* Input: `words`.
18+
* Output: Scratch statement block definition that is appended to the current script stack.
19+
*
20+
* @param words See function signature for accepted input values.
21+
* @returns Scratch statement block definition that is appended to the current script stack.
22+
* @example
23+
* ```ts
24+
* import { speakAndWait } from 'hikkaku/blocks'
25+
*
26+
* speakAndWait('hello')
27+
* ```
28+
*/
29+
export const speakAndWait = (words: PrimitiveSource<HikkakuString>) => {
30+
return block('text2speech_speakAndWait', {
31+
inputs: {
32+
WORDS: fromPrimitiveSource(InputType.String, words, 'hello'),
33+
},
34+
})
35+
}
36+
37+
/**
38+
* Sets the text-to-speech voice.
39+
*
40+
* Input: `voice`.
41+
* Output: Scratch statement block definition that is appended to the current script stack.
42+
*
43+
* @param voice See function signature for accepted input values.
44+
* @returns Scratch statement block definition that is appended to the current script stack.
45+
* @example
46+
* ```ts
47+
* import { setVoice } from 'hikkaku/blocks'
48+
*
49+
* setVoice('TENOR')
50+
* ```
51+
*/
52+
export function setVoice(voice: TextToSpeechVoice): ReturnType<typeof block>
53+
export function setVoice(
54+
voice: PrimitiveSource<HikkakuString>,
55+
): ReturnType<typeof block>
56+
export function setVoice(voice: PrimitiveSource<HikkakuString>) {
57+
return block('text2speech_setVoice', {
58+
inputs: {
59+
VOICE: menuInput(voice, menuOfTextToSpeechVoice),
60+
},
61+
})
62+
}
63+
64+
export const menuOfTextToSpeechVoice = (voice: TextToSpeechVoice = 'ALTO') => {
65+
return valueBlock<HikkakuString>('text2speech_menu_voices', {
66+
fields: {
67+
voices: [voice, null],
68+
},
69+
isShadow: true,
70+
})
71+
}
72+
73+
/**
74+
* Sets the text-to-speech language.
75+
*
76+
* Input: `language`.
77+
* Output: Scratch statement block definition that is appended to the current script stack.
78+
*
79+
* @param language See function signature for accepted input values.
80+
* @returns Scratch statement block definition that is appended to the current script stack.
81+
* @example
82+
* ```ts
83+
* import { setLanguage } from 'hikkaku/blocks'
84+
*
85+
* setLanguage('ja')
86+
* ```
87+
*/
88+
export const setLanguage = (language: PrimitiveSource<HikkakuString>) => {
89+
return block('text2speech_setLanguage', {
90+
inputs: {
91+
LANGUAGE: menuInput(language, menuOfTextToSpeechLanguage),
92+
},
93+
})
94+
}
95+
96+
export const menuOfTextToSpeechLanguage = (language: string = 'en') => {
97+
return valueBlock<HikkakuString>('text2speech_menu_languages', {
98+
fields: {
99+
languages: [language, null],
100+
},
101+
isShadow: true,
102+
})
103+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, test } from 'vite-plus/test'
2+
import { block, createBlocks } from '../core/composer'
3+
import { say } from './looks'
4+
import { getViewerLanguage, translate } from './translate'
5+
6+
describe('blocks/translate', () => {
7+
test('creates translate extension blocks', () => {
8+
const blocks = createBlocks(() => {
9+
block('event_whenflagclicked', { topLevel: true })
10+
say(translate('hello', 'ja'))
11+
say(getViewerLanguage())
12+
})
13+
14+
const opcodes = Object.values(blocks).map((b) => b.opcode)
15+
expect(opcodes).toContain('translate_getTranslate')
16+
expect(opcodes).toContain('translate_menu_languages')
17+
expect(opcodes).toContain('translate_getViewerLanguage')
18+
})
19+
})
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { InputType } from 'sb3-types/enum'
2+
import { fromPrimitiveSource, menuInput } from '../core/block-helper'
3+
import { valueBlock } from '../core/composer'
4+
import type { HikkakuString, PrimitiveSource } from '../core/types'
5+
6+
/**
7+
* Translates words to a target language.
8+
*
9+
* Input: `words`, `language`.
10+
* Output: Scratch reporter block definition that can be used as an input value in other blocks.
11+
*
12+
* @param words See function signature for accepted input values.
13+
* @param language See function signature for accepted input values.
14+
* @returns Scratch reporter block definition that can be used as an input value in other blocks.
15+
* @example
16+
* ```ts
17+
* import { translate } from 'hikkaku/blocks'
18+
*
19+
* translate('hello', 'ja')
20+
* ```
21+
*/
22+
export const translate = (
23+
words: PrimitiveSource<HikkakuString>,
24+
language: PrimitiveSource<HikkakuString>,
25+
) => {
26+
return valueBlock<HikkakuString>('translate_getTranslate', {
27+
inputs: {
28+
WORDS: fromPrimitiveSource(InputType.String, words, 'hello'),
29+
LANGUAGE: menuInput(language, menuOfTranslateLanguage),
30+
},
31+
})
32+
}
33+
34+
export const menuOfTranslateLanguage = (language: string = 'ja') => {
35+
return valueBlock<HikkakuString>('translate_menu_languages', {
36+
fields: {
37+
languages: [language, null],
38+
},
39+
isShadow: true,
40+
})
41+
}
42+
43+
/**
44+
* Returns viewer language.
45+
*
46+
* Input: none.
47+
* Output: Scratch reporter block definition that can be used as an input value in other blocks.
48+
*
49+
* @returns Scratch reporter block definition that can be used as an input value in other blocks.
50+
* @example
51+
* ```ts
52+
* import { getViewerLanguage } from 'hikkaku/blocks'
53+
*
54+
* getViewerLanguage()
55+
* ```
56+
*/
57+
export const getViewerLanguage = () => {
58+
return valueBlock<HikkakuString>('translate_getViewerLanguage', {})
59+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, test } from 'vite-plus/test'
2+
import { createBlocks } from '../core/composer'
3+
import { say } from './looks'
4+
import {
5+
setVideoTransparency,
6+
videoOn,
7+
videoToggle,
8+
whenMotionGreaterThan,
9+
} from './videosensing'
10+
11+
describe('blocks/videosensing', () => {
12+
test('creates video sensing extension blocks', () => {
13+
const blocks = createBlocks(() => {
14+
whenMotionGreaterThan(10, () => {
15+
setVideoTransparency(50)
16+
})
17+
say(videoOn('motion', 'this sprite'))
18+
videoToggle('on')
19+
})
20+
21+
const opcodes = Object.values(blocks).map((b) => b.opcode)
22+
expect(opcodes).toContain('videoSensing_whenMotionGreaterThan')
23+
expect(opcodes).toContain('videoSensing_videoOn')
24+
expect(opcodes).toContain('videoSensing_menu_ATTRIBUTE')
25+
expect(opcodes).toContain('videoSensing_menu_SUBJECT')
26+
expect(opcodes).toContain('videoSensing_videoToggle')
27+
expect(opcodes).toContain('videoSensing_menu_VIDEO_STATE')
28+
expect(opcodes).toContain('videoSensing_setVideoTransparency')
29+
30+
const hat = Object.values(blocks).find(
31+
(b) => b.opcode === 'videoSensing_whenMotionGreaterThan',
32+
)
33+
expect(hat?.topLevel).toBe(true)
34+
})
35+
})
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { InputType } from 'sb3-types/enum'
2+
import { fromPrimitiveSource, menuInput } from '../core/block-helper'
3+
import { attachStack, block, valueBlock } from '../core/composer'
4+
import type {
5+
HikkakuNumber,
6+
HikkakuString,
7+
PrimitiveSource,
8+
} from '../core/types'
9+
10+
export type VideoSensingAttribute = 'motion' | 'direction'
11+
export type VideoSensingSubject = 'this sprite' | 'stage'
12+
export type VideoState = 'on' | 'off' | 'on-flipped'
13+
14+
/**
15+
* Runs when motion is greater than a threshold.
16+
*
17+
* Input: `reference`, `stack`.
18+
* Output: Scratch statement block definition that is appended to the current script stack.
19+
*
20+
* @param reference See function signature for accepted input values.
21+
* @param stack See function signature for accepted input values. Optional.
22+
* @returns Scratch statement block definition that is appended to the current script stack.
23+
* @example
24+
* ```ts
25+
* import { whenMotionGreaterThan } from 'hikkaku/blocks'
26+
*
27+
* whenMotionGreaterThan(10, () => {})
28+
* ```
29+
*/
30+
export const whenMotionGreaterThan = (
31+
reference: PrimitiveSource<HikkakuNumber>,
32+
stack?: () => void,
33+
) => {
34+
const res = block('videoSensing_whenMotionGreaterThan', {
35+
topLevel: true,
36+
inputs: {
37+
REFERENCE: fromPrimitiveSource(InputType.Number, reference, 10),
38+
},
39+
})
40+
attachStack(res.id, stack)
41+
return res
42+
}
43+
44+
/**
45+
* Returns motion or direction detected from a subject.
46+
*
47+
* Input: `attribute`, `subject`.
48+
* Output: Scratch reporter block definition that can be used as an input value in other blocks.
49+
*
50+
* @param attribute See function signature for accepted input values.
51+
* @param subject See function signature for accepted input values.
52+
* @returns Scratch reporter block definition that can be used as an input value in other blocks.
53+
* @example
54+
* ```ts
55+
* import { videoOn } from 'hikkaku/blocks'
56+
*
57+
* videoOn('motion', 'this sprite')
58+
* ```
59+
*/
60+
export const videoOn = (
61+
attribute: PrimitiveSource<HikkakuString>,
62+
subject: PrimitiveSource<HikkakuString>,
63+
) => {
64+
return valueBlock<HikkakuNumber>('videoSensing_videoOn', {
65+
inputs: {
66+
ATTRIBUTE: menuInput(attribute, menuOfVideoSensingAttribute),
67+
SUBJECT: menuInput(subject, menuOfVideoSensingSubject),
68+
},
69+
})
70+
}
71+
72+
export const menuOfVideoSensingAttribute = (
73+
attribute: VideoSensingAttribute = 'motion',
74+
) => {
75+
return valueBlock<HikkakuString>('videoSensing_menu_ATTRIBUTE', {
76+
fields: {
77+
ATTRIBUTE: [attribute, null],
78+
},
79+
isShadow: true,
80+
})
81+
}
82+
83+
export const menuOfVideoSensingSubject = (
84+
subject: VideoSensingSubject = 'this sprite',
85+
) => {
86+
return valueBlock<HikkakuString>('videoSensing_menu_SUBJECT', {
87+
fields: {
88+
SUBJECT: [subject, null],
89+
},
90+
isShadow: true,
91+
})
92+
}
93+
94+
/**
95+
* Turns video on/off/flipped.
96+
*
97+
* Input: `state`.
98+
* Output: Scratch statement block definition that is appended to the current script stack.
99+
*
100+
* @param state See function signature for accepted input values.
101+
* @returns Scratch statement block definition that is appended to the current script stack.
102+
* @example
103+
* ```ts
104+
* import { videoToggle } from 'hikkaku/blocks'
105+
*
106+
* videoToggle('on')
107+
* ```
108+
*/
109+
export const videoToggle = (state: PrimitiveSource<HikkakuString>) => {
110+
return block('videoSensing_videoToggle', {
111+
inputs: {
112+
VIDEO_STATE: menuInput(state, menuOfVideoSensingState),
113+
},
114+
})
115+
}
116+
117+
export const menuOfVideoSensingState = (state: VideoState = 'on') => {
118+
return valueBlock<HikkakuString>('videoSensing_menu_VIDEO_STATE', {
119+
fields: {
120+
VIDEO_STATE: [state, null],
121+
},
122+
isShadow: true,
123+
})
124+
}
125+
126+
/**
127+
* Sets video transparency.
128+
*
129+
* Input: `transparency`.
130+
* Output: Scratch statement block definition that is appended to the current script stack.
131+
*
132+
* @param transparency See function signature for accepted input values.
133+
* @returns Scratch statement block definition that is appended to the current script stack.
134+
* @example
135+
* ```ts
136+
* import { setVideoTransparency } from 'hikkaku/blocks'
137+
*
138+
* setVideoTransparency(50)
139+
* ```
140+
*/
141+
export const setVideoTransparency = (
142+
transparency: PrimitiveSource<HikkakuNumber>,
143+
) => {
144+
return block('videoSensing_setVideoTransparency', {
145+
inputs: {
146+
TRANSPARENCY: fromPrimitiveSource(InputType.Number, transparency, 50),
147+
},
148+
})
149+
}

0 commit comments

Comments
 (0)