Skip to content
Closed
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
3 changes: 3 additions & 0 deletions packages/hikkaku/src/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export * from './pen'
export * from './procedures'
export * from './sensing'
export * from './sound'
export * from './text2speech'
export * from './translate'
export * from './videosensing'
19 changes: 19 additions & 0 deletions packages/hikkaku/src/blocks/text2speech.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, test } from 'vite-plus/test'
import { block, createBlocks } from '../core/composer'
import { setLanguage, setVoice, speakAndWait } from './text2speech'

describe('blocks/text2speech', () => {
test('creates text to speech extension blocks', () => {
const blocks = createBlocks(() => {
block('event_whenflagclicked', { topLevel: true })
setVoice('TENOR')
setLanguage('ja')
speakAndWait('hello')
})

const opcodes = Object.values(blocks).map((b) => b.opcode)
expect(opcodes).toContain('text2speech_setVoice')
expect(opcodes).toContain('text2speech_setLanguage')
expect(opcodes).toContain('text2speech_speakAndWait')
})
})
36 changes: 36 additions & 0 deletions packages/hikkaku/src/blocks/text2speech.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { InputType } from 'sb3-types/enum'
import { fromPrimitiveSource } from '../core/block-helper'
import { block } from '../core/composer'
import type { HikkakuString, PrimitiveSource } from '../core/types'

export type TextToSpeechVoice =
| 'ALTO'
| 'TENOR'
| 'SQUEAK'
| 'GIANT'
| 'KITTEN'
| 'GOOGLE'

export const speakAndWait = (words: PrimitiveSource<HikkakuString>) => {
return block('text2speech_speakAndWait', {
inputs: {
WORDS: fromPrimitiveSource(InputType.String, words, 'hello'),
},
})
Comment on lines +14 to +19

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exported block factories in this new module are missing the JSDoc pattern used across packages/hikkaku/src/blocks (description, inputs/outputs, @example, etc.), which is likely important for the reference doc generation mentioned in the PR description. Consider adding doc comments for the public exports (similar to music.ts / looks.ts) so generated docs remain consistent.

Copilot uses AI. Check for mistakes.
}

export const setVoice = (voice: PrimitiveSource<HikkakuString>) => {
return block('text2speech_setVoice', {
inputs: {
VOICE: fromPrimitiveSource(InputType.String, voice, 'ALTO'),
},
})
Comment on lines +22 to +27

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextToSpeechVoice is declared, but setVoice accepts PrimitiveSource<HikkakuString> and encodes VOICE as a generic string input. If this block is meant to reflect Scratch’s voice dropdown, consider using the TextToSpeechVoice type in the API (and encoding as a dropdown via fields or menuInput + a shadow menu block) so callers get type safety and the generated SB3 better matches Scratch semantics.

Copilot uses AI. Check for mistakes.
}

export const setLanguage = (language: PrimitiveSource<HikkakuString>) => {
return block('text2speech_setLanguage', {
inputs: {
LANGUAGE: fromPrimitiveSource(InputType.String, language, 'en'),
},
})
}
18 changes: 18 additions & 0 deletions packages/hikkaku/src/blocks/translate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, test } from 'vite-plus/test'
import { block, createBlocks } from '../core/composer'
import { say } from './looks'
import { getViewerLanguage, translate } from './translate'

describe('blocks/translate', () => {
test('creates translate extension blocks', () => {
const blocks = createBlocks(() => {
block('event_whenflagclicked', { topLevel: true })
say(translate('hello', 'ja'))
say(getViewerLanguage())
})

const opcodes = Object.values(blocks).map((b) => b.opcode)
expect(opcodes).toContain('translate_getTranslate')
expect(opcodes).toContain('translate_getViewerLanguage')
})
})
20 changes: 20 additions & 0 deletions packages/hikkaku/src/blocks/translate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { InputType } from 'sb3-types/enum'
import { fromPrimitiveSource } from '../core/block-helper'
import { valueBlock } from '../core/composer'
import type { HikkakuString, PrimitiveSource } from '../core/types'

export const translate = (
words: PrimitiveSource<HikkakuString>,
language: PrimitiveSource<HikkakuString>,
) => {
return valueBlock<HikkakuString>('translate_getTranslate', {
inputs: {
WORDS: fromPrimitiveSource(InputType.String, words, 'hello'),
LANGUAGE: fromPrimitiveSource(InputType.String, language, 'ja'),
},
})
Comment on lines +6 to +15

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translate_getTranslate’s LANGUAGE argument is typically a dropdown/menu in Scratch, but here it’s represented as a plain string input. Elsewhere, dropdowns are modeled as fields or menuInput with a shadow menu block (e.g. sensing_keypressed). Consider aligning with that approach so generated SB3 retains expected menu/shadow structure and callers get clearer intent.

Copilot uses AI. Check for mistakes.
}
Comment on lines +6 to +16

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new module’s public exports are missing JSDoc comments, while most existing block factories in this directory include structured JSDoc used for reference docs/examples (see blocks/looks.ts, blocks/music.ts, etc.). Adding doc comments here would keep the generated docs consistent with the rest of the blocks API.

Copilot uses AI. Check for mistakes.

export const getViewerLanguage = () => {
return valueBlock<HikkakuString>('translate_getViewerLanguage', {})
}
27 changes: 27 additions & 0 deletions packages/hikkaku/src/blocks/videosensing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, test } from 'vite-plus/test'
import { block, createBlocks } from '../core/composer'
import { say } from './looks'
import {
setVideoTransparency,
videoOn,
videoToggle,
whenMotionGreaterThan,
} from './videosensing'

describe('blocks/videosensing', () => {
test('creates video sensing extension blocks', () => {
const blocks = createBlocks(() => {
block('event_whenflagclicked', { topLevel: true })
whenMotionGreaterThan(10)
say(videoOn('motion', 'this sprite'))
videoToggle('on')
setVideoTransparency(50)
})

const opcodes = Object.values(blocks).map((b) => b.opcode)
expect(opcodes).toContain('videoSensing_whenMotionGreaterThan')
expect(opcodes).toContain('videoSensing_videoOn')
expect(opcodes).toContain('videoSensing_videoToggle')
expect(opcodes).toContain('videoSensing_setVideoTransparency')
})
})
52 changes: 52 additions & 0 deletions packages/hikkaku/src/blocks/videosensing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { InputType } from 'sb3-types/enum'
import { fromPrimitiveSource } from '../core/block-helper'
import { block, valueBlock } from '../core/composer'
import type {
HikkakuNumber,
HikkakuString,
PrimitiveSource,
} from '../core/types'

export type VideoSensingAttribute = 'motion' | 'direction'
export type VideoSensingSubject = 'this sprite' | 'stage'
export type VideoState = 'on' | 'off' | 'on-flipped'

export const whenMotionGreaterThan = (
reference: PrimitiveSource<HikkakuNumber>,
) => {
return block('videoSensing_whenMotionGreaterThan', {
inputs: {
REFERENCE: fromPrimitiveSource(InputType.Number, reference, 10),
},
})
Comment on lines +14 to +21

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

videoSensing_whenMotionGreaterThan is a hat block in Scratch, but this factory creates it as a normal statement (no topLevel: true and no stack attachment). This can lead to invalid scripts (hat blocks chained under another block) and prevents the block from being used like other hat helpers (e.g. whenFlagClicked). Consider adding topLevel: true and an optional stack?: () => void parameter using attachStack (as done in blocks/events.ts).

Copilot uses AI. Check for mistakes.
}
Comment on lines +14 to +22

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new module adds public block factories but does not include the JSDoc-style documentation used throughout packages/hikkaku/src/blocks (inputs/outputs + @example). Since the PR mentions documentation generation, consider adding JSDoc for these exports to match the rest of the blocks API.

Copilot uses AI. Check for mistakes.

export const videoOn = (
attribute: PrimitiveSource<HikkakuString>,
subject: PrimitiveSource<HikkakuString>,
) => {
return valueBlock<HikkakuNumber>('videoSensing_videoOn', {
inputs: {
ATTRIBUTE: fromPrimitiveSource(InputType.String, attribute, 'motion'),
SUBJECT: fromPrimitiveSource(InputType.String, subject, 'this sprite'),
Comment on lines +25 to +31

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dropdown-like parameters (ATTRIBUTE, SUBJECT) are currently encoded as plain string inputs via fromPrimitiveSource. Elsewhere in this codebase, menu/dropdown inputs are typically modeled with menuInput(...) + a shadow menu reporter block (see sensing.getKeyPressed / menuOfKeyOptions) or as fields when the value is a true field. Encoding this as a primitive string input may lose the expected menu shadow block / UI semantics in generated SB3. Consider switching to a menu helper (or fields) consistent with other dropdowns.

Suggested change
attribute: PrimitiveSource<HikkakuString>,
subject: PrimitiveSource<HikkakuString>,
) => {
return valueBlock<HikkakuNumber>('videoSensing_videoOn', {
inputs: {
ATTRIBUTE: fromPrimitiveSource(InputType.String, attribute, 'motion'),
SUBJECT: fromPrimitiveSource(InputType.String, subject, 'this sprite'),
attribute: VideoSensingAttribute,
subject: VideoSensingSubject,
) => {
return valueBlock<HikkakuNumber>('videoSensing_videoOn', {
fields: {
ATTRIBUTE: [attribute, null],
SUBJECT: [subject, null],

Copilot uses AI. Check for mistakes.
},
})
}

export const videoToggle = (state: PrimitiveSource<HikkakuString>) => {
return block('videoSensing_videoToggle', {
inputs: {
VIDEO_STATE: fromPrimitiveSource(InputType.String, state, 'on'),
Comment on lines +36 to +39

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VIDEO_STATE is a dropdown in Scratch, but it is currently represented as a generic string input. To match other dropdown/menu patterns in this repo (and Scratch’s SB3 structure), consider modeling this as fields: { VIDEO_STATE: [...] } or via menuInput + a shadow menu reporter block, rather than fromPrimitiveSource(InputType.String, ...).

Suggested change
export const videoToggle = (state: PrimitiveSource<HikkakuString>) => {
return block('videoSensing_videoToggle', {
inputs: {
VIDEO_STATE: fromPrimitiveSource(InputType.String, state, 'on'),
export const videoToggle = (state: VideoState) => {
return block('videoSensing_videoToggle', {
fields: {
VIDEO_STATE: [state, null],

Copilot uses AI. Check for mistakes.
},
})
}

export const setVideoTransparency = (
transparency: PrimitiveSource<HikkakuNumber>,
) => {
return block('videoSensing_setVideoTransparency', {
inputs: {
TRANSPARENCY: fromPrimitiveSource(InputType.Number, transparency, 50),
},
})
}
Loading