Skip to content

Commit e1a4d8b

Browse files
committed
chore: add fish example
1 parent 2691bd0 commit e1a4d8b

139 files changed

Lines changed: 3399 additions & 155 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,7 @@ A project file is a JSON object that configures the Blockflow editor. It is pass
2727
"title": "My Project",
2828
"sb3": "https://example.com/project.sb3",
2929
"ui": {
30-
"allowExtensions": false,
31-
"showCostumesTab": false,
32-
"showSoundsTab": false,
33-
"showBuiltinCostumes": false,
34-
"showBuiltinSounds": false,
35-
"showBuiltinBackdrops": false,
36-
"showBuiltinSprites": false
30+
"allowExtensions": false
3731
},
3832
"toolbox": {
3933
"categories": ["motion", "looks", "events", "control"],
@@ -53,27 +47,52 @@ A project file is a JSON object that configures the Blockflow editor. It is pass
5347
"image": "https://example.com/step2.png"
5448
}
5549
],
56-
"costumes": [
57-
{ "name": "Cat", "url": "https://example.com/cat.svg" },
58-
{ "name": "Photo", "url": "https://example.com/photo.png", "centerX": 100, "centerY": 80 }
59-
],
60-
"sounds": [
61-
{ "name": "Meow", "url": "https://example.com/meow.mp3" }
62-
],
63-
"backdrops": [
64-
{ "name": "Beach", "url": "https://example.com/beach.png" }
65-
],
66-
"sprites": [
67-
{
68-
"name": "Dog",
69-
"costumes": [
70-
{ "name": "dog-a", "url": "https://example.com/dog-a.svg" }
71-
],
72-
"sounds": [
73-
{ "name": "bark", "url": "https://example.com/bark.mp3" }
74-
]
75-
}
76-
]
50+
"costumes": {
51+
"enabled": false,
52+
"showBuiltin": false,
53+
"tags": [
54+
{ "key": "animal", "label": "Animal" },
55+
{ "key": "person", "label": "Person" }
56+
],
57+
"library": [
58+
{ "name": "Cat", "url": "https://example.com/cat.svg", "tags": ["animal"] },
59+
{ "name": "Photo", "url": "https://example.com/photo.png", "centerX": 100, "centerY": 80, "tags": ["person"] }
60+
]
61+
},
62+
"sounds": {
63+
"enabled": false,
64+
"showBuiltin": false,
65+
"tags": [
66+
{ "key": "effect", "label": "Effect" }
67+
],
68+
"library": [
69+
{ "name": "Meow", "url": "https://example.com/meow.mp3", "tags": ["effect"] }
70+
]
71+
},
72+
"backdrops": {
73+
"showBuiltin": false,
74+
"library": [
75+
{ "name": "Beach", "url": "https://example.com/beach.png" }
76+
]
77+
},
78+
"sprites": {
79+
"showBuiltin": false,
80+
"tags": [
81+
{ "key": "animal", "label": "Animal" }
82+
],
83+
"library": [
84+
{
85+
"name": "Dog",
86+
"tags": ["animal"],
87+
"costumes": [
88+
{ "name": "dog-a", "url": "https://example.com/dog-a.svg" }
89+
],
90+
"sounds": [
91+
{ "name": "bark", "url": "https://example.com/bark.mp3" }
92+
]
93+
}
94+
]
95+
}
7796
}
7897
```
7998

@@ -82,19 +101,27 @@ A project file is a JSON object that configures the Blockflow editor. It is pass
82101
| `title` | **(required)** Project name displayed in the editor. |
83102
| `sb3` | URL to a `.sb3` file to load into the VM. |
84103
| `ui.allowExtensions` | Show or hide the extensions button. |
85-
| `ui.showCostumesTab` | Show or hide the Costumes/Backdrops tab. |
86-
| `ui.showSoundsTab` | Show or hide the Sounds tab. |
87-
| `ui.showBuiltinCostumes` | Show or hide built-in costumes in the costume library (default: `true`). |
88-
| `ui.showBuiltinSounds` | Show or hide built-in sounds in the sound library (default: `true`). |
89-
| `ui.showBuiltinBackdrops` | Show or hide built-in backdrops in the backdrop library (default: `true`). |
90-
| `ui.showBuiltinSprites` | Show or hide built-in sprites in the sprite library (default: `true`). |
91104
| `toolbox.categories` | Array of enabled block categories (`motion`, `looks`, `sound`, `events`, `control`, `sensing`, `operators`, `variables`, `myBlocks`). |
92105
| `toolbox.blocks` | Object mapping category names to arrays of allowed block opcodes for fine-grained filtering. |
93106
| `steps` | Array of tutorial steps, each with `title`, `text`, `image`, and/or `video`. |
94-
| `costumes` | Array of custom costumes, each with `name`, `url`, and optional `centerX`/`centerY` (default: center of image). Appear in the costume library. |
95-
| `sounds` | Array of custom sounds, each with `name` and `url`. Appear in the sound library. |
96-
| `backdrops` | Array of custom backdrops, each with `name`, `url`, and optional `centerX`/`centerY` (default: center of image). Appear in the backdrop library. |
97-
| `sprites` | Array of custom sprites, each with `name`, `costumes` (array), and `sounds` (array). Appear in the sprite library. |
107+
| `costumes` | Custom costumes. Can be a flat array of items (backward compatible) or an object with `enabled`, `showBuiltin`, `tags`, and `library`. |
108+
| `costumes.enabled` | Show or hide the Costumes/Backdrops tab (default: `true`). |
109+
| `costumes.showBuiltin` | Show or hide built-in costumes in the costume library (default: `true`). |
110+
| `costumes.tags` | Array of tag definitions (`{key, label}`) for filtering. Appended to built-in tags; shown alone when `showBuiltin` is `false`. |
111+
| `costumes.library` | Array of costume items, each with `name`, `url`, optional `centerX`/`centerY`, and optional `tags` (array of tag keys). |
112+
| `sounds` | Custom sounds. Can be a flat array or an object with `enabled`, `showBuiltin`, `tags`, and `library`. |
113+
| `sounds.enabled` | Show or hide the Sounds tab (default: `true`). |
114+
| `sounds.showBuiltin` | Show or hide built-in sounds in the sound library (default: `true`). |
115+
| `sounds.tags` | Array of tag definitions (`{key, label}`) for filtering. Appended to built-in tags; shown alone when `showBuiltin` is `false`. |
116+
| `sounds.library` | Array of sound items, each with `name`, `url`, and optional `tags` (array of tag keys). |
117+
| `backdrops` | Custom backdrops. Can be a flat array or an object with `showBuiltin`, `tags`, and `library`. |
118+
| `backdrops.showBuiltin` | Show or hide built-in backdrops in the backdrop library (default: `true`). |
119+
| `backdrops.tags` | Array of tag definitions (`{key, label}`) for filtering. Appended to built-in tags; shown alone when `showBuiltin` is `false`. |
120+
| `backdrops.library` | Array of backdrop items, each with `name`, `url`, optional `centerX`/`centerY`, and optional `tags` (array of tag keys). |
121+
| `sprites` | Custom sprites. Can be a flat array or an object with `showBuiltin`, `tags`, and `library`. |
122+
| `sprites.showBuiltin` | Show or hide built-in sprites in the sprite library (default: `true`). |
123+
| `sprites.tags` | Array of tag definitions (`{key, label}`) for filtering. Appended to built-in tags; shown alone when `showBuiltin` is `false`. |
124+
| `sprites.library` | Array of sprite items, each with `name`, `costumes`, `sounds`, and optional `tags` (array of tag keys). |
98125

99126
Relative URLs in `sb3`, `image`, `video`, and asset `url` fields are resolved relative to the project file URL.
100127

packages/scratch-gui/src/containers/backdrop-library.jsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,18 @@ class BackdropLibrary extends React.Component {
5656
}
5757
render () {
5858
const mergedAssets = this.mergeDynamicAssets();
59+
const {backdropTags: customBackdropTags, showBuiltinBackdrops} = this.props;
60+
let tags = backdropTags;
61+
if (showBuiltinBackdrops === false) {
62+
tags = customBackdropTags || [];
63+
} else if (customBackdropTags) {
64+
tags = backdropTags.concat(customBackdropTags);
65+
}
5966
return (
6067
<LibraryComponent
6168
data={mergedAssets}
6269
id="backdropLibrary"
63-
tags={backdropTags}
70+
tags={tags}
6471
title={this.props.intl.formatMessage(messages.libraryTitle)}
6572
onItemSelected={this.handleItemSelect}
6673
onRequestClose={this.props.onRequestClose}
@@ -71,14 +78,28 @@ class BackdropLibrary extends React.Component {
7178

7279
const mapStateToProps = state => {
7380
const projectFile = state.scratchGui.projectFile.projectFile;
81+
const da = state.scratchGui.dynamicAssets;
82+
let showBuiltinBackdrops;
83+
if (da.showBuiltinBackdrops !== null) {
84+
showBuiltinBackdrops = da.showBuiltinBackdrops;
85+
} else if (projectFile && projectFile.ui) {
86+
showBuiltinBackdrops = projectFile.ui.showBuiltinBackdrops;
87+
}
7488
return {
75-
dynamicBackdrops: state.scratchGui.dynamicAssets.backdrops,
76-
showBuiltinBackdrops: projectFile && projectFile.ui ?
77-
projectFile.ui.showBuiltinBackdrops : undefined
89+
dynamicBackdrops: da.backdrops,
90+
showBuiltinBackdrops,
91+
backdropTags: da.backdropTags
7892
};
7993
};
8094

8195
BackdropLibrary.propTypes = {
96+
backdropTags: PropTypes.arrayOf(PropTypes.shape({
97+
tag: PropTypes.string,
98+
intlLabel: PropTypes.shape({
99+
id: PropTypes.string,
100+
defaultMessage: PropTypes.string
101+
})
102+
})),
82103
dynamicBackdrops: PropTypes.arrayOf(costumeShape),
83104
intl: intlShape.isRequired,
84105
onRequestClose: PropTypes.func,

packages/scratch-gui/src/containers/costume-library.jsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,18 @@ class CostumeLibrary extends React.PureComponent {
5555
}
5656
render () {
5757
const data = this.mergeDynamicAssets();
58+
const {costumeTags, showBuiltinCostumes} = this.props;
59+
let tags = spriteTags;
60+
if (showBuiltinCostumes === false) {
61+
tags = costumeTags || [];
62+
} else if (costumeTags) {
63+
tags = spriteTags.concat(costumeTags);
64+
}
5865
return (
5966
<LibraryComponent
6067
data={data}
6168
id="costumeLibrary"
62-
tags={spriteTags}
69+
tags={tags}
6370
title={this.props.intl.formatMessage(messages.libraryTitle)}
6471
onItemSelected={this.handleItemSelected}
6572
onRequestClose={this.props.onRequestClose}
@@ -70,14 +77,28 @@ class CostumeLibrary extends React.PureComponent {
7077

7178
const mapStateToProps = state => {
7279
const projectFile = state.scratchGui.projectFile.projectFile;
80+
const da = state.scratchGui.dynamicAssets;
81+
let showBuiltinCostumes;
82+
if (da.showBuiltinCostumes !== null) {
83+
showBuiltinCostumes = da.showBuiltinCostumes;
84+
} else if (projectFile && projectFile.ui) {
85+
showBuiltinCostumes = projectFile.ui.showBuiltinCostumes;
86+
}
7387
return {
74-
dynamicCostumes: state.scratchGui.dynamicAssets.costumes,
75-
showBuiltinCostumes: projectFile && projectFile.ui ?
76-
projectFile.ui.showBuiltinCostumes : undefined
88+
dynamicCostumes: da.costumes,
89+
costumeTags: da.costumeTags,
90+
showBuiltinCostumes
7791
};
7892
};
7993

8094
CostumeLibrary.propTypes = {
95+
costumeTags: PropTypes.arrayOf(PropTypes.shape({
96+
tag: PropTypes.string,
97+
intlLabel: PropTypes.shape({
98+
id: PropTypes.string,
99+
defaultMessage: PropTypes.string
100+
})
101+
})),
81102
dynamicCostumes: PropTypes.arrayOf(costumeShape),
82103
intl: intlShape.isRequired,
83104
onRequestClose: PropTypes.func,

packages/scratch-gui/src/containers/sound-library.jsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,21 @@ class SoundLibrary extends React.PureComponent {
183183
};
184184
});
185185

186+
const {soundTags: customSoundTags, showBuiltinSounds} = this.props;
187+
let tags = soundTags;
188+
if (showBuiltinSounds === false) {
189+
tags = customSoundTags || [];
190+
} else if (customSoundTags) {
191+
tags = soundTags.concat(customSoundTags);
192+
}
193+
186194
return (
187195
<LibraryComponent
188196
showPlayButton
189197
data={soundLibraryThumbnailData}
190198
id="soundLibrary"
191199
setStopHandler={this.setStopHandler}
192-
tags={soundTags}
200+
tags={tags}
193201
title={this.props.intl.formatMessage(messages.libraryTitle)}
194202
onItemMouseEnter={this.handleItemMouseEnter}
195203
onItemMouseLeave={this.handleItemMouseLeave}
@@ -207,16 +215,30 @@ SoundLibrary.propTypes = {
207215
onNewSound: PropTypes.func.isRequired,
208216
onRequestClose: PropTypes.func,
209217
showBuiltinSounds: PropTypes.bool,
218+
soundTags: PropTypes.arrayOf(PropTypes.shape({
219+
tag: PropTypes.string,
220+
intlLabel: PropTypes.shape({
221+
id: PropTypes.string,
222+
defaultMessage: PropTypes.string
223+
})
224+
})),
210225
vm: PropTypes.instanceOf(VM).isRequired
211226
};
212227

213228
const mapStateToProps = state => {
214229
const projectFile = state.scratchGui.projectFile.projectFile;
230+
const da = state.scratchGui.dynamicAssets;
231+
let showBuiltinSounds;
232+
if (da.showBuiltinSounds !== null) {
233+
showBuiltinSounds = da.showBuiltinSounds;
234+
} else if (projectFile && projectFile.ui) {
235+
showBuiltinSounds = projectFile.ui.showBuiltinSounds;
236+
}
215237
return {
216-
dynamicSounds: state.scratchGui.dynamicAssets.sounds,
238+
dynamicSounds: da.sounds,
217239
isRtl: state.locales.isRtl,
218-
showBuiltinSounds: projectFile && projectFile.ui ?
219-
projectFile.ui.showBuiltinSounds : undefined
240+
showBuiltinSounds,
241+
soundTags: da.soundTags
220242
};
221243
};
222244

packages/scratch-gui/src/containers/sprite-library.jsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,18 @@ class SpriteLibrary extends React.PureComponent {
5353
}
5454
render () {
5555
const data = this.mergeDynamicAssets();
56+
const {spriteTags: customSpriteTags, showBuiltinSprites} = this.props;
57+
let tags = spriteTags;
58+
if (showBuiltinSprites === false) {
59+
tags = customSpriteTags || [];
60+
} else if (customSpriteTags) {
61+
tags = spriteTags.concat(customSpriteTags);
62+
}
5663
return (
5764
<LibraryComponent
5865
data={data}
5966
id="spriteLibrary"
60-
tags={spriteTags}
67+
tags={tags}
6168
title={this.props.intl.formatMessage(messages.libraryTitle)}
6269
onItemSelected={this.handleItemSelect}
6370
onRequestClose={this.props.onRequestClose}
@@ -68,10 +75,17 @@ class SpriteLibrary extends React.PureComponent {
6875

6976
const mapStateToProps = state => {
7077
const projectFile = state.scratchGui.projectFile.projectFile;
78+
const da = state.scratchGui.dynamicAssets;
79+
let showBuiltinSprites;
80+
if (da.showBuiltinSprites !== null) {
81+
showBuiltinSprites = da.showBuiltinSprites;
82+
} else if (projectFile && projectFile.ui) {
83+
showBuiltinSprites = projectFile.ui.showBuiltinSprites;
84+
}
7185
return {
72-
dynamicSprites: state.scratchGui.dynamicAssets.sprites,
73-
showBuiltinSprites: projectFile && projectFile.ui ?
74-
projectFile.ui.showBuiltinSprites : undefined
86+
dynamicSprites: da.sprites,
87+
showBuiltinSprites,
88+
spriteTags: da.spriteTags
7589
};
7690
};
7791

@@ -81,6 +95,13 @@ SpriteLibrary.propTypes = {
8195
onActivateBlocksTab: PropTypes.func.isRequired,
8296
onRequestClose: PropTypes.func,
8397
showBuiltinSprites: PropTypes.bool,
98+
spriteTags: PropTypes.arrayOf(PropTypes.shape({
99+
tag: PropTypes.string,
100+
intlLabel: PropTypes.shape({
101+
id: PropTypes.string,
102+
defaultMessage: PropTypes.string
103+
})
104+
})),
84105
vm: PropTypes.instanceOf(VM).isRequired
85106
};
86107

packages/scratch-gui/src/lib/fetch-project-file.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,38 @@ const fetchProjectFile = async function (url) {
5959
});
6060
};
6161

62-
projectFile.sounds = resolveAssetUrls(projectFile.sounds);
63-
projectFile.costumes = resolveAssetUrls(projectFile.costumes);
64-
projectFile.backdrops = resolveAssetUrls(projectFile.backdrops);
62+
// Normalize an asset-type field that may be a flat array or {tags, library, showBuiltin}
63+
const normalizeAssetField = field => {
64+
if (!field) return field;
65+
if (Array.isArray(field)) return resolveAssetUrls(field);
66+
if (typeof field === 'object') {
67+
const normalized = Object.assign({}, field);
68+
if (Array.isArray(normalized.library)) {
69+
normalized.library = resolveAssetUrls(normalized.library);
70+
}
71+
return normalized;
72+
}
73+
return field;
74+
};
75+
76+
projectFile.sounds = normalizeAssetField(projectFile.sounds);
77+
projectFile.costumes = normalizeAssetField(projectFile.costumes);
78+
projectFile.backdrops = normalizeAssetField(projectFile.backdrops);
6579

6680
// Resolve sprite asset URLs (nested costumes and sounds)
67-
if (Array.isArray(projectFile.sprites)) {
68-
projectFile.sprites = projectFile.sprites.map(sprite => {
69-
const resolved = Object.assign({}, sprite);
70-
resolved.costumes = resolveAssetUrls(resolved.costumes);
71-
resolved.sounds = resolveAssetUrls(resolved.sounds);
72-
return resolved;
81+
const normalizeSprites = sprites => {
82+
if (!sprites) return sprites;
83+
const list = Array.isArray(sprites) ? sprites : (sprites.library || []);
84+
const resolved = list.map(sprite => {
85+
const s = Object.assign({}, sprite);
86+
s.costumes = resolveAssetUrls(s.costumes);
87+
s.sounds = resolveAssetUrls(s.sounds);
88+
return s;
7389
});
74-
}
90+
if (Array.isArray(sprites)) return resolved;
91+
return Object.assign({}, sprites, {library: resolved});
92+
};
93+
projectFile.sprites = normalizeSprites(projectFile.sprites);
7594

7695
return projectFile;
7796
};

0 commit comments

Comments
 (0)