scratch-storage 2.2.0 → 2.3.0

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.
Files changed (41) hide show
  1. package/.circleci/config.yml +1 -1
  2. package/dist/node/scratch-storage.js +125 -57
  3. package/dist/node/scratch-storage.js.map +1 -1
  4. package/dist/web/scratch-storage.js +125 -57
  5. package/dist/web/scratch-storage.js.map +1 -1
  6. package/dist/web/scratch-storage.min.js +125 -57
  7. package/dist/web/scratch-storage.min.js.map +1 -1
  8. package/jest.config.js +5 -0
  9. package/package.json +10 -8
  10. package/src/BuiltinHelper.js +3 -3
  11. package/src/FetchWorkerTool.js +8 -1
  12. package/src/ScratchStorage.js +9 -0
  13. package/src/scratchFetch.js +51 -11
  14. package/test/.eslintrc.js +5 -1
  15. package/test/{mocks/mockFetch.js → __mocks__/cross-fetch.js} +30 -16
  16. package/test/fixtures/.gitattributes +3 -0
  17. package/test/fixtures/assets/117504922.json +196 -0
  18. package/test/fixtures/assets/66895930177178ea01d9e610917f8acf.png +0 -0
  19. package/test/fixtures/assets/6e8bd9ae68fdb02b7e1e3df656a75635.svg +37 -0
  20. package/test/fixtures/assets/7e24c99c1b853e52f8e7f9004416fa34.png +0 -0
  21. package/test/fixtures/assets/83c36d806dc92327b9e7049a565c6bff.wav +0 -0
  22. package/test/fixtures/assets/f88bf1935daea28f8ca098462a31dbb0.svg +35 -0
  23. package/test/fixtures/assets/fe5e3566965f9de793beeffce377d054.jpg +0 -0
  24. package/test/fixtures/known-assets.js +60 -0
  25. package/test/integration/download-known-assets.test.js +141 -0
  26. package/test/transformers/.eslintrc.js +8 -0
  27. package/test/transformers/arraybuffer-loader.js +11 -0
  28. package/test/unit/{add-helper.js → add-helper.test.js} +16 -20
  29. package/test/unit/fetch-tool.test.js +56 -0
  30. package/test/unit/load-default-assets.test.js +59 -0
  31. package/test/unit/metadata.test.js +129 -0
  32. package/webpack.config.js +4 -0
  33. package/.nyc_output/c826ae6b-4ae2-4d8e-bdb2-162df5761fa7.json +0 -1
  34. package/.nyc_output/ec2d52a1-5131-4dd0-9c23-59106f297082.json +0 -1
  35. package/.nyc_output/processinfo/c826ae6b-4ae2-4d8e-bdb2-162df5761fa7.json +0 -1
  36. package/.nyc_output/processinfo/ec2d52a1-5131-4dd0-9c23-59106f297082.json +0 -1
  37. package/.nyc_output/processinfo/index.json +0 -1
  38. package/test/integration/download-known-assets.js +0 -111
  39. package/test/unit/fetch-tool.js +0 -60
  40. package/test/unit/load-default-assets.js +0 -48
  41. package/test/unit/metadata.js +0 -136
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ transform: {
3
+ '\\.(png|svg|wav)$': '<rootDir>/test/transformers/arraybuffer-loader.js'
4
+ }
5
+ };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "scratch-storage",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Load and store project and asset files for Scratch 3.0",
5
5
  "license": "BSD-3-Clause",
6
6
  "homepage": "https://github.com/LLK/scratch-storage#readme",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/LLK/scratch-storage.git",
10
- "sha": "5c6588073bb3c9ba4500403054d6409db2379286"
10
+ "sha": "0537bdad85576ae09cf31765bd65b7450f31308b"
11
11
  },
12
12
  "main": "./dist/node/scratch-storage.js",
13
13
  "browser": "./src/index.js",
@@ -15,11 +15,11 @@
15
15
  "build": "webpack --progress --colors --bail",
16
16
  "coverage": "tap ./test/{unit,integration}/*.js --coverage --coverage-report=lcov",
17
17
  "commitmsg": "commitlint -e $GIT_PARAMS",
18
- "lint": "eslint .",
19
- "tap-integration": "tap ./test/integration/*.js",
20
- "tap-unit": "tap ./test/unit/*.js",
21
- "tap": "npm run tap-unit && npm run tap-integration",
22
- "test": "npm run lint && npm run tap",
18
+ "test": "npm run test:lint && jest \"test[\\\\/](unit|integration)\"",
19
+ "test:clearCache": "jest --clearCache",
20
+ "test:lint": "eslint .",
21
+ "test:integration": "jest \"test[\\\\/]integration\"",
22
+ "test:unit": "jest \"test[\\\\/]unit\"",
23
23
  "version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
24
24
  "watch": "webpack --progress --colors --watch",
25
25
  "semantic-release": "semantic-release"
@@ -45,17 +45,19 @@
45
45
  "@commitlint/cli": "8.2.0",
46
46
  "@commitlint/config-conventional": "8.2.0",
47
47
  "@commitlint/travis-cli": "8.2.0",
48
+ "@types/jest": "29.5.1",
48
49
  "babel-eslint": "10.1.0",
49
50
  "babel-loader": "8.0.6",
50
51
  "cz-conventional-changelog": "3.3.0",
51
52
  "eslint": "7.27.0",
52
53
  "eslint-config-scratch": "6.0.0",
54
+ "eslint-plugin-jest": "27.2.1",
53
55
  "eslint-plugin-react": "7.24.0",
54
56
  "file-loader": "4.1.0",
55
57
  "husky": "1.3.1",
58
+ "jest": "29.5.0",
56
59
  "json": "^9.0.4",
57
60
  "semantic-release": "^15.10.5",
58
- "tap": "16.3.4",
59
61
  "uglifyjs-webpack-plugin": "2.2.0",
60
62
  "webpack": "4.46.0",
61
63
  "webpack-cli": "3.1.2"
@@ -24,7 +24,7 @@ const DefaultAssets = [
24
24
  format: DataFormat.PNG,
25
25
  id: null,
26
26
  data: Buffer.from(
27
- require('!arraybuffer-loader!./builtins/defaultBitmap.png') // eslint-disable-line global-require
27
+ require('./builtins/defaultBitmap.png') // eslint-disable-line global-require
28
28
  )
29
29
  },
30
30
  {
@@ -32,7 +32,7 @@ const DefaultAssets = [
32
32
  format: DataFormat.WAV,
33
33
  id: null,
34
34
  data: Buffer.from(
35
- require('!arraybuffer-loader!./builtins/defaultSound.wav') // eslint-disable-line global-require
35
+ require('./builtins/defaultSound.wav') // eslint-disable-line global-require
36
36
  )
37
37
  },
38
38
  {
@@ -40,7 +40,7 @@ const DefaultAssets = [
40
40
  format: DataFormat.SVG,
41
41
  id: null,
42
42
  data: Buffer.from(
43
- require('!arraybuffer-loader!./builtins/defaultVector.svg') // eslint-disable-line global-require
43
+ require('./builtins/defaultVector.svg') // eslint-disable-line global-require
44
44
  )
45
45
  }
46
46
  ];
@@ -1,4 +1,4 @@
1
- const {applyMetadata} = require('./scratchFetch');
1
+ const {Headers, applyMetadata} = require('./scratchFetch');
2
2
 
3
3
  /**
4
4
  * Get and send assets with a worker that uses fetch.
@@ -92,6 +92,13 @@ class PrivateFetchWorkerTool {
92
92
  const augmentedOptions = applyMetadata(
93
93
  Object.assign({method: 'GET'}, options)
94
94
  );
95
+ // the Fetch spec says options.headers could be:
96
+ // "A Headers object, an object literal, or an array of two-item arrays to set request's headers."
97
+ // structured clone (postMessage) doesn't support Headers objects
98
+ // so turn it into an array of two-item arrays to make it to the worker intact
99
+ if (augmentedOptions && augmentedOptions.headers instanceof Headers) {
100
+ augmentedOptions.headers = Array.from(augmentedOptions.headers.entries());
101
+ }
95
102
  this.worker.postMessage({
96
103
  id,
97
104
  url,
@@ -6,6 +6,7 @@ const WebHelper = require('./WebHelper');
6
6
  const _Asset = require('./Asset');
7
7
  const _AssetType = require('./AssetType');
8
8
  const _DataFormat = require('./DataFormat');
9
+ const _scratchFetch = require('./scratchFetch');
9
10
 
10
11
  class ScratchStorage {
11
12
  constructor () {
@@ -51,6 +52,14 @@ class ScratchStorage {
51
52
  return _DataFormat;
52
53
  }
53
54
 
55
+ /**
56
+ * Access the `scratchFetch` module within this library.
57
+ * @return {module} the scratchFetch module, with properties for `scratchFetch`, `setMetadata`, etc.
58
+ */
59
+ get scratchFetch () {
60
+ return _scratchFetch;
61
+ }
62
+
54
63
  /**
55
64
  * @deprecated Please use the `Asset` member of a storage instance instead.
56
65
  * @return {Asset} - the `Asset` class constructor.
@@ -1,4 +1,4 @@
1
- const {fetch, Headers} = require('cross-fetch');
1
+ const crossFetch = require('cross-fetch');
2
2
 
3
3
  /**
4
4
  * Metadata header names
@@ -7,16 +7,40 @@ const {fetch, Headers} = require('cross-fetch');
7
7
  */
8
8
  const RequestMetadata = {
9
9
  /** The ID of the project associated with this request */
10
- ProjectId: 'X-ProjectId',
10
+ ProjectId: 'X-Project-ID',
11
11
  /** The ID of the project run associated with this request */
12
- RunId: 'X-RunId'
12
+ RunId: 'X-Run-ID'
13
13
  };
14
14
 
15
15
  /**
16
- * Metadata for requests
17
- * @type {Map<string, string>}
16
+ * Metadata headers for requests
17
+ * @type {Headers}
18
18
  */
19
- const metadata = new Map();
19
+ const metadata = new crossFetch.Headers();
20
+
21
+ /**
22
+ * Check if there is any metadata to apply.
23
+ * @returns {boolean} true if `metadata` has contents, or false if it is empty.
24
+ */
25
+ const hasMetadata = () => {
26
+ /* global self */
27
+ const searchParams = (
28
+ typeof self !== 'undefined' &&
29
+ self &&
30
+ self.location &&
31
+ self.location.search &&
32
+ self.location.search.split(/[?&]/)
33
+ ) || [];
34
+ if (!searchParams.includes('scratchMetadata=1')) {
35
+ // for now, disable this feature unless scratchMetadata=1
36
+ // TODO: remove this check once we're sure the feature works correctly in production
37
+ return false;
38
+ }
39
+ for (const _ of metadata) {
40
+ return true;
41
+ }
42
+ return false;
43
+ };
20
44
 
21
45
  /**
22
46
  * Non-destructively merge any metadata state (if any) with the provided options object (if any).
@@ -28,12 +52,15 @@ const metadata = new Map();
28
52
  * @returns {RequestInit|undefined} the provided options parameter without modification, or a new options object.
29
53
  */
30
54
  const applyMetadata = options => {
31
- if (metadata.size > 0) {
55
+ if (hasMetadata()) {
32
56
  const augmentedOptions = Object.assign({}, options);
33
- augmentedOptions.headers = new Headers(Array.from(metadata));
57
+ augmentedOptions.headers = new crossFetch.Headers(metadata);
34
58
  if (options && options.headers) {
35
- const overrideHeaders =
36
- options.headers instanceof Headers ? options.headers : new Headers(options.headers);
59
+ // the Fetch spec says options.headers could be:
60
+ // "A Headers object, an object literal, or an array of two-item arrays to set request's headers."
61
+ // turn it into a Headers object to be sure of how to interact with it
62
+ const overrideHeaders = options.headers instanceof crossFetch.Headers ?
63
+ options.headers : new crossFetch.Headers(options.headers);
37
64
  for (const [name, value] of overrideHeaders.entries()) {
38
65
  augmentedOptions.headers.set(name, value);
39
66
  }
@@ -53,7 +80,7 @@ const applyMetadata = options => {
53
80
  */
54
81
  const scratchFetch = (resource, options) => {
55
82
  const augmentedOptions = applyMetadata(options);
56
- return fetch(resource, augmentedOptions);
83
+ return crossFetch.fetch(resource, augmentedOptions);
57
84
  };
58
85
 
59
86
  /**
@@ -78,9 +105,22 @@ const unsetMetadata = name => {
78
105
  module.exports = {
79
106
  default: scratchFetch,
80
107
 
108
+ Headers: crossFetch.Headers,
81
109
  RequestMetadata,
82
110
  applyMetadata,
83
111
  scratchFetch,
84
112
  setMetadata,
85
113
  unsetMetadata
86
114
  };
115
+
116
+ if (process.env.NODE_ENV === 'development') {
117
+ /**
118
+ * Retrieve a named request metadata item.
119
+ * Only for use in tests.
120
+ * @param {RequestMetadata} name The name of the metadata item to retrieve.
121
+ * @returns {any} value The value of the metadata item, or `undefined` if it was not found.
122
+ */
123
+ const getMetadata = name => metadata.get(name);
124
+
125
+ module.exports.getMetadata = getMetadata;
126
+ }
package/test/.eslintrc.js CHANGED
@@ -1,3 +1,7 @@
1
1
  module.exports = {
2
- extends: ['scratch/node', 'scratch/es6']
2
+ extends: ['scratch/es6', 'plugin:jest/recommended'],
3
+ env: {
4
+ jest: true
5
+ },
6
+ plugins: ['jest']
3
7
  };
@@ -1,6 +1,8 @@
1
1
  const TextEncoder = require('util').TextEncoder;
2
- const {Headers} = require('cross-fetch');
2
+ const crossFetch = jest.requireActual('cross-fetch');
3
+ const knownAssets = require('../fixtures/known-assets.js');
3
4
 
5
+ const Headers = crossFetch.Headers;
4
6
  const successText = 'successful response';
5
7
 
6
8
  /**
@@ -37,28 +39,40 @@ const mockFetch = (resource, options) => {
37
39
  options.mockFetchTestData.headers = new Headers(options.headers);
38
40
  options.mockFetchTestData.headersCount = Array.from(options.mockFetchTestData.headers).length;
39
41
  }
40
- switch (resource) {
41
- case '200':
42
+
43
+ const assetInfo = knownAssets[resource];
44
+ if (assetInfo) {
42
45
  results.ok = true;
43
46
  results.status = 200;
44
- results.text = () => Promise.resolve(successText);
45
- results.arrayBuffer = () => Promise.resolve(new TextEncoder().encode(successText));
46
- break;
47
- case '404':
48
- results.ok = false;
49
- results.status = 404;
50
- break;
51
- case '500':
52
- results.ok = false;
53
- results.status = 500;
54
- break;
55
- default:
56
- throw new Error('unimplemented');
47
+ results.arrayBuffer = () => Promise.resolve(assetInfo.content);
48
+ } else {
49
+ switch (resource) {
50
+ case '200':
51
+ results.ok = true;
52
+ results.status = 200;
53
+ results.text = () => Promise.resolve(successText);
54
+ results.arrayBuffer = () => Promise.resolve(new TextEncoder().encode(successText));
55
+ break;
56
+ case '404':
57
+ results.ok = false;
58
+ results.status = 404;
59
+ break;
60
+ case '500':
61
+ results.ok = false;
62
+ results.status = 500;
63
+ break;
64
+ default:
65
+ throw new Error(`mockFetch doesn't know how to download: ${resource}`);
66
+ }
57
67
  }
58
68
  return Promise.resolve(results);
59
69
  };
60
70
 
71
+ // Mimic the cross-fetch module, but replace its `fetch` with `mockFetch` and add a few extras
61
72
  module.exports = {
73
+ ...crossFetch, // Headers, Request, Response, etc.
74
+ default: mockFetch,
75
+ fetch: mockFetch,
62
76
  mockFetch,
63
77
  successText
64
78
  };
@@ -0,0 +1,3 @@
1
+ # Assets in this directory must be preserved exactly
2
+ # to ensure the MD5 hash matches the expected value.
3
+ * binary
@@ -0,0 +1,196 @@
1
+ {
2
+ "objName": "Stage",
3
+ "costumes": [{
4
+ "costumeName": "backdrop1",
5
+ "baseLayerID": -1,
6
+ "baseLayerMD5": "ba5d7bbc200d7925664a0878421c4ed2.png",
7
+ "bitmapResolution": 2,
8
+ "rotationCenterX": 480,
9
+ "rotationCenterY": 360
10
+ }],
11
+ "currentCostumeIndex": 0,
12
+ "penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png",
13
+ "penLayerID": -1,
14
+ "tempoBPM": 60,
15
+ "videoAlpha": 0.5,
16
+ "children": [{
17
+ "objName": "seymour cropped",
18
+ "scripts": [[89, 102, [["whenGreenFlag"], ["setSizeTo:", 85]]]],
19
+ "sounds": [{
20
+ "soundName": "pop",
21
+ "soundID": -1,
22
+ "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
23
+ "sampleCount": 258,
24
+ "rate": 11025,
25
+ "format": ""
26
+ }],
27
+ "costumes": [{
28
+ "costumeName": "SeymourPapert001",
29
+ "baseLayerID": -1,
30
+ "baseLayerMD5": "2c8559602a79c7ec929636c3a5ea0291.png",
31
+ "bitmapResolution": 2,
32
+ "rotationCenterX": 204,
33
+ "rotationCenterY": 262
34
+ }],
35
+ "currentCostumeIndex": 0,
36
+ "scratchX": 115,
37
+ "scratchY": -28,
38
+ "scale": 0.85,
39
+ "direction": 90,
40
+ "rotationStyle": "normal",
41
+ "isDraggable": false,
42
+ "indexInLibrary": 2,
43
+ "visible": true,
44
+ "spriteInfo": {
45
+ }
46
+ },
47
+ {
48
+ "objName": "Sprite1",
49
+ "scripts": [[48,
50
+ 24,
51
+ [["whenGreenFlag"],
52
+ ["setSizeTo:", 110],
53
+ ["wait:elapsed:from:", 1],
54
+ ["say:", "Hi. I want to introduce you to Seymour Papert."],
55
+ ["doPlaySoundAndWait", "papert1"],
56
+ ["say:", "Seymour developed Logo, the first programming language for kids"],
57
+ ["doPlaySoundAndWait", "papert2"],
58
+ ["say:", "He believed that kids learn best"],
59
+ ["doPlaySoundAndWait", "papert3"],
60
+ ["say:", "when they are given the chance to create things and express themselves"],
61
+ ["doPlaySoundAndWait", "papert4"],
62
+ ["say:", "When we developed Scratch, we were very inspired by Seymour's ideas"],
63
+ ["doPlaySoundAndWait", "papert5"],
64
+ ["say:", "So you might call Seymour the grandfather of Scratch"],
65
+ ["doPlaySoundAndWait", "papert6"],
66
+ ["say:", "Sadly, Seymour passed away last week, on July 31, 2016"],
67
+ ["doPlaySoundAndWait", "papert7"],
68
+ ["say:", "But every time you create and share a Scratch project"],
69
+ ["doPlaySoundAndWait", "papert8"],
70
+ ["say:", "you're helping to keep Seymour's spirit alive"],
71
+ ["doPlaySoundAndWait", "papert9"],
72
+ ["say:", "So Scratch on!"],
73
+ ["doPlaySoundAndWait", "papert10"],
74
+ ["say:", "And Papert on!"],
75
+ ["doPlaySoundAndWait", "papert11"],
76
+ ["wait:elapsed:from:", 5],
77
+ ["say:", ""]]]],
78
+ "sounds": [{
79
+ "soundName": "papert1",
80
+ "soundID": -1,
81
+ "md5": "876c45f175060737364334daed9366a1.wav",
82
+ "sampleCount": 84993,
83
+ "rate": 22050,
84
+ "format": ""
85
+ },
86
+ {
87
+ "soundName": "papert2",
88
+ "soundID": -1,
89
+ "md5": "e439ca64ef29692ed611bb769fdbe543.wav",
90
+ "sampleCount": 103425,
91
+ "rate": 22050,
92
+ "format": ""
93
+ },
94
+ {
95
+ "soundName": "papert3",
96
+ "soundID": -1,
97
+ "md5": "0bd55bc2b3ff5741bafe981decdaca5b.wav",
98
+ "sampleCount": 54273,
99
+ "rate": 22050,
100
+ "format": ""
101
+ },
102
+ {
103
+ "soundName": "papert4",
104
+ "soundID": -1,
105
+ "md5": "123abc35f1934ed471436c59f7b99e2f.wav",
106
+ "sampleCount": 93185,
107
+ "rate": 22050,
108
+ "format": ""
109
+ },
110
+ {
111
+ "soundName": "papert5",
112
+ "soundID": -1,
113
+ "md5": "cd9f672266016a0e55152b2a3398c8d9.wav",
114
+ "sampleCount": 113665,
115
+ "rate": 22050,
116
+ "format": ""
117
+ },
118
+ {
119
+ "soundName": "papert6",
120
+ "soundID": -1,
121
+ "md5": "d38287334764b7d4cd2c5db59548234d.wav",
122
+ "sampleCount": 84993,
123
+ "rate": 22050,
124
+ "format": ""
125
+ },
126
+ {
127
+ "soundName": "papert7",
128
+ "soundID": -1,
129
+ "md5": "fab6bf9923265a999bcfea1e62384a2c.wav",
130
+ "sampleCount": 144385,
131
+ "rate": 22050,
132
+ "format": ""
133
+ },
134
+ {
135
+ "soundName": "papert8",
136
+ "soundID": -1,
137
+ "md5": "6826373fa0554c582f920cc65620395e.wav",
138
+ "sampleCount": 82945,
139
+ "rate": 22050,
140
+ "format": ""
141
+ },
142
+ {
143
+ "soundName": "papert9",
144
+ "soundID": -1,
145
+ "md5": "c6b907cbfe0081748ae83cb3bb6aa6d8.wav",
146
+ "sampleCount": 74753,
147
+ "rate": 22050,
148
+ "format": ""
149
+ },
150
+ {
151
+ "soundName": "papert10",
152
+ "soundID": -1,
153
+ "md5": "f455697888f2e7b36072a2189fc78bbc.wav",
154
+ "sampleCount": 39937,
155
+ "rate": 22050,
156
+ "format": ""
157
+ },
158
+ {
159
+ "soundName": "papert11",
160
+ "soundID": -1,
161
+ "md5": "fb58460f1cd8629eae3b3c58148984ad.wav",
162
+ "sampleCount": 54273,
163
+ "rate": 22050,
164
+ "format": ""
165
+ }],
166
+ "costumes": [{
167
+ "costumeName": "-mres-cartoon",
168
+ "baseLayerID": -1,
169
+ "baseLayerMD5": "dd88c3596176f24479a8e8ec9b6c8278.png",
170
+ "bitmapResolution": 2,
171
+ "rotationCenterX": 122,
172
+ "rotationCenterY": 204
173
+ }],
174
+ "currentCostumeIndex": 0,
175
+ "scratchX": -137.95,
176
+ "scratchY": -71,
177
+ "scale": 1.1,
178
+ "direction": 90,
179
+ "rotationStyle": "normal",
180
+ "isDraggable": false,
181
+ "indexInLibrary": 1,
182
+ "visible": true,
183
+ "spriteInfo": {
184
+ }
185
+ }],
186
+ "info": {
187
+ "spriteCount": 2,
188
+ "hasCloudData": false,
189
+ "projectID": "117504922",
190
+ "flashVersion": "MAC 22,0,0,209",
191
+ "swfVersion": "v448",
192
+ "userAgent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/51.0.2704.103 Safari\/537.36",
193
+ "scriptCount": 2,
194
+ "videoOn": false
195
+ }
196
+ }
@@ -0,0 +1,37 @@
1
+ <svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <g>
3
+ <g id="Layer_8">
4
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M23.754,82.506c-2.871-1.402-10.511-2.518-14.771-6.695&#xD;&#xA;&#x9;&#x9;c-9.431-9.246-3.824-21.732,0.704-21.797c3.611-0.061,3.363,5.482,4.622,11.145c0.875,3.939,1.673,6.988,6.407,9.674&#xD;&#xA;&#x9;&#x9;c4.735,2.688,6.918,2.916,6.918,2.916s3.258,0.791,1.596,2.562C27.568,82.076,23.754,82.506,23.754,82.506z"/>
5
+ <path fill="#FFFFFF" d="M14.291,66.369c-0.115-0.459-0.222-0.928-0.33-1.413c-1.172-5.271-0.941-10.432-4.303-10.377&#xD;&#xA;&#x9;&#x9;c-3.433,0.05-7.53,7.771-4.19,15.285c0.193-0.143,0.399-0.336,0.569-0.588c0.451-0.664,0.821-2.969,2.654-3.688&#xD;&#xA;&#x9;&#x9;C9.989,65.082,12.778,65.869,14.291,66.369z"/>
6
+ </g>
7
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.543,61.473c0,0,1.455,2.342,1.519,4.969&#xD;&#xA;&#x9;c0.065,2.723-0.351,6.984,1.804,8.158c3.381,1.842,9.959-0.463,10.174,5.191c0.211,5.652-10.152,3.623-13.299,2.547&#xD;&#xA;&#x9;c-2.695-0.926-4.559-1.758-5.18-5.182c0,0-3.764-13.969-0.196-15.59C50.933,59.945,52.543,61.473,52.543,61.473z"/>
8
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M14.61,89.584c0,0,1.504,10.391-5.922,8.387&#xD;&#xA;&#x9;c-5.586-1.51-5.705-20.68-0.406-20.691c3.237-0.006,4.425,2.859,4.425,2.859s4.385,4.812,6.95,4.312&#xD;&#xA;&#x9;c2.565-0.502,7.978-6.703,7.978-6.703s2.875-2.539,6.146,0.023c3.271,2.561,1.521,6.178,1.521,6.178s-6.07,7.072-11.014,8.072&#xD;&#xA;&#x9;c-4.945,1.002-9.375-2.568-11.58-3.451"/>
9
+ <g id="Layer_6">
10
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M42.435,76.238c0,0,4.866,4.062,8.166,7.52&#xD;&#xA;&#x9;&#x9;c4.384,4.592-3.339,15.039-3.339,15.039s11.234,6.467,2.998,11c-2.657,1.467-15.661-13.225-15.661-13.225s7.979-7.475,4.972-10.172&#xD;&#xA;&#x9;&#x9;c-4.964-4.449-10.771-4.959-11.6-9.824c-0.502-2.957,0.613-4.502,2.658-7.053c1.354-1.689,2.084-3.182,2.084-3.182&#xD;&#xA;&#x9;&#x9;s1.135-7.238,4.289-8.254c3.153-1.014,8.842,0.057,10.758,0.395c1.914,0.338,6.645,1.877,4.617,9.922c0,0-1.451,3.771-2.789,5.762&#xD;&#xA;&#x9;&#x9;c-1.604,2.389-2.854,3.924-4.08,4.867"/>
11
+ <path fill="#FFFFFF" d="M50.182,72.631c0,0,2.55-2.748-2.551-5.494c-5.102-2.746-7.449-0.016-9.024,1.572&#xD;&#xA;&#x9;&#x9;c-2.731,2.742,3.334,7.453,3.334,7.453l3.383,2.564c0,0,1.916-1.582,2.701-2.957C48.807,74.396,50.182,72.631,50.182,72.631"/>
12
+ </g>
13
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M24.428,65.395c0.939,0.057,3.625,0.213,6.064,0.391&#xD;&#xA;&#x9;c3.014,0.217,8.451-0.451,5.801-5.098c-2.649-4.65-6.74-3.674-11.961-3.754c-2.812-0.043-5.785-1.412-7.322,1.895&#xD;&#xA;&#x9;c-1.534,3.312-5.051,13.338,2.313,15.396c7.366,2.057,4.618-8.137,4.95-10.457"/>
14
+ <g id="Layer_2">
15
+ <path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M51.709,10.156c-1.54-0.143-4.75-0.316-6.518-0.231&#xD;&#xA;&#x9;&#x9;c-4.728,0.225-9.224,1.928-9.224,1.928L22.949,3.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224&#xD;&#xA;&#x9;&#x9;c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.728,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823&#xD;&#xA;&#x9;&#x9;s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L61.822,1.647L54.37,8.851L51.709,10.156z"/>
16
+ <path fill="#FFFFFF" d="M75.42,31.066l-2.483-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277&#xD;&#xA;&#x9;&#x9;l-7.784,3.417c0,0-8.437,0.928-8.739,6.081C31.048,54.704,45.1,59.478,50.425,59.783c2.905,0.167,8.235-0.337,12.277-1.14&#xD;&#xA;&#x9;&#x9;c17.752-3.235,22.551-13.92,21.309-19.635c-1.242-5.714-7.977-7.196-7.977-7.196L75.42,31.066z"/>
17
+ <path fill="none" stroke="#231F20" stroke-width="1.2" d="M9.673,42.155c0,0,4.107,0.374,5.974,0.268&#xD;&#xA;&#x9;&#x9;c1.865-0.107,5.492-0.587,5.492-0.587"/>
18
+ <path fill="none" stroke="#231F20" stroke-width="1.2" d="M80.656,36.671c0,0,4.549-0.743,6.859-1.549&#xD;&#xA;&#x9;&#x9;c2.715-0.942,4.543-2.545,4.543-2.545"/>
19
+ <path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.337,37.909c0,0-2.384-1.777-6.117-3.43&#xD;&#xA;&#x9;&#x9;c-4.134-1.831-6.405-2.303-6.405-2.303"/>
20
+ <path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.117,42.622c0,0,2.726,1.104,5.533,1.385&#xD;&#xA;&#x9;&#x9;c2.77,0.276,4.647,0.11,4.647,0.11"/>
21
+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M51.349,10.212&#xD;&#xA;&#x9;&#x9;c2.84,0.7,3.888,1.469,3.888,1.469"/>
22
+ <line fill="none" stroke="#000000" x1="32.898" y1="9.684" x2="38.956" y2="14.042"/>
23
+ </g>
24
+ <g id="Layer_5">
25
+ <path fill="#FFFFFF" stroke="#231F20" d="M70.84,21.366c2.924,4.479,3.033,9.591,0.242,11.415&#xD;&#xA;&#x9;&#x9;c-2.793,1.825-7.426-0.332-10.354-4.813c-2.932-4.48-3.037-9.589-0.244-11.415C63.275,14.73,67.913,16.884,70.84,21.366z"/>
26
+ <path fill="#231F20" d="M70.089,28.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956&#xD;&#xA;&#x9;&#x9;c0-1.08,0.81-1.958,1.803-1.958C69.287,26.564,70.089,27.442,70.089,28.522"/>
27
+ </g>
28
+ <g id="Layer_7">
29
+ <path fill="#FFFFFF" stroke="#231F20" d="M46.867,24.619c2.926,4.48,2.619,9.862-0.681,12.015&#xD;&#xA;&#x9;&#x9;c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C38.891,18.253,43.938,20.137,46.867,24.619z"/>
30
+ <path fill="#231F20" d="M45.079,30.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957&#xD;&#xA;&#x9;&#x9;c0-1.08,0.811-1.957,1.803-1.957C44.274,28.55,45.079,29.427,45.079,30.507"/>
31
+ </g>
32
+ <path fill="#5E4A42" stroke="#000000" d="M58.766,33.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345&#xD;&#xA;&#x9;c-1.283,0.142-6.125-2.353-6.195-3.919C54.206,33.355,57.055,33.926,58.766,33.926z"/>
33
+ <g id="Layer_4">
34
+ <path fill="none" stroke="#231F20" stroke-width="1.2" d="M45.774,41.235c0,0,10.347,3.054,14.217,3.897&#xD;&#xA;&#x9;&#x9;c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C44.176,51.863,44.345,45.975,45.774,41.235z"/>
35
+ </g>
36
+ </g>
37
+ </svg>
@@ -0,0 +1,35 @@
1
+ <svg version="1.1" id="Scratch_Cat_A" x="0px" y="0px" width="94.227px" height="104.333px" viewBox="0 -2 94.227 104.333" enable-background="new 0 -2 94.227 104.333" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <g>
3
+ <path id="Back_Leg" fill="#FAA51D" stroke="#000000" d="M38.988,80.967c0,0-8.832,6.201-17.07,8.412l0.086,0.215&#xD;&#xA;&#x9;c1.247,1.824,5.869,7.498-0.334,9.496c-5.333,1.718-15.12-13.104-10.821-15.901c2.626-1.713,4.892-0.252,4.892-0.252&#xD;&#xA;&#x9;s3.475-1.069,6.001-2.345c4.304-2.162,5.784-3.453,5.784-3.453s4.185-4.307,6.856-4.137C37.051,73.172,42.44,77.715,38.988,80.967z&#xD;&#xA;&#x9; M19.646,90.09l2.271-0.711"/>
4
+ <g id="Tail">
5
+ <path id="Tail_1_" fill="#FAA51D" stroke="#000000" d="M23.295,73.217c-2.415-0.451-5.305-1.311-7.742-3.504&#xD;&#xA;&#x9;&#x9;c-5.451-4.906-7.194-13-11.048-10.914c-3.856,2.088-3.782,15.166,8.353,19.195c4.182,1.391,7.998,1.395,11.091,1.312&#xD;&#xA;&#x9;&#x9;c0.812-0.026,7.718-0.655,10.079-4.075c2.361-3.419,0.719-4.271-0.09-4.744C33.128,70.016,26.711,73.855,23.295,73.217z"/>
6
+ <path id="Tail_Tip" fill="#FFFFFF" d="M4.641,59.107c-1.437,0.778-2.256,3.397-1.994,6.374c0.141,1.594,1.104,5.652,4.968,9.072&#xD;&#xA;&#x9;&#x9;c0.275-0.545-0.975-2.936,0.742-4.379c1.837-1.543,4.86-1.225,5.853-1.061c-1.663-1.812-3.004-3.812-4.176-5.64&#xD;&#xA;&#x9;&#x9;C7.751,59.922,6.475,58.116,4.641,59.107z"/>
7
+ </g>
8
+ <path id="R_Arm" fill="#FAA51D" stroke="#000000" d="M52.94,69.096c0,0,1.234,0.166,4.744,3.677c3.509,3.509,6.025,2.16,8.911,0.724&#xD;&#xA;&#x9;c2.877-1.442,10.536-6.127,6.489-9.817c-4.05-3.688-6.207,1.146-9.716,2.405c-3.511,1.26-5.06-2.487-6.856-4.287&#xD;&#xA;&#x9;c-0.59-0.594-1.188-1.098-1.729-1.506c0,0-0.972-0.758-1.905,2.791C51.943,66.624,50.933,67.627,52.94,69.096z"/>
9
+ <g id="Front_Leg_x2F_Body_1_">
10
+ <path id="Front_Leg_x2F_Body" fill="#FAA51D" stroke="#000000" d="M47.524,76.224c1.188-0.912,2.396-2.401,3.951-4.714&#xD;&#xA;&#x9;&#x9;c1.296-1.926,2.699-5.577,2.699-5.577c0.875-2.521,1.935-6.576-1.901-7.296c-1.553-0.291-4.079-0.098-7.67-0.776&#xD;&#xA;&#x9;&#x9;c-3.593-0.681-6.798-2.522-9.517,2.233c-2.719,4.757-9.591,8.271-1.057,16.562c0,0,4.901,3.842,10.765,9.639&#xD;&#xA;&#x9;&#x9;c4.832,4.774,12.044,10.603,12.044,10.603s18.973,2.188,19.534-0.694c1.923-9.789-14.775-6.91-14.775-6.91&#xD;&#xA;&#x9;&#x9;s-4.604-3.933-6.727-5.795c-3.477-3.058-11.125-10.771-11.125-10.771"/>
11
+ <path id="Belly" fill="#FFFFFF" d="M49.74,64.551C44.8,61.895,42.526,64.536,41,66.073c-2.645,2.654,3.442,6.61,3.442,6.61&#xD;&#xA;&#x9;&#x9;l3.062,3.089c2.999-3.496,4.579-5.874,5.292-7.934C52.654,66.897,51.918,65.725,49.74,64.551z"/>
12
+ </g>
13
+ <path id="L_Arm" fill="#FAA51D" stroke="#000000" d="M30.697,67.43c0.749-0.571,2.89-2.202,4.854-3.657&#xD;&#xA;&#x9;c2.428-1.799,6.117-5.849,1.077-7.646c-5.04-1.801-7.508,1.604-11.52,4.945c-2.159,1.801-5.308,2.698-4.319,6.209&#xD;&#xA;&#x9;c0.993,3.512,4.862,13.407,11.789,10.17c6.93-3.238-1.799-9.181-3.06-11.156"/>
14
+ <g id="Head">
15
+ <g id="Head_1_">
16
+ <path id="Head_Fill" fill="#FAA51D" stroke="#000000" d="M86.452,37.457c-1.472-6.447-8.891-7.579-8.891-7.579&#xD;&#xA;&#x9;&#x9;&#x9;s0.176-5.351-2.628-10.347c-1.856-3.307-8.351-8.042-8.351-8.042l-2.854-11.55l-7.43,7.32l-2.854,1.105&#xD;&#xA;&#x9;&#x9;&#x9;c-1.539-0.143-4.718-0.312-6.485-0.227c-4.729,0.225-9.227,2.036-9.227,2.036L24.854,1.649l2.233,18.91&#xD;&#xA;&#x9;&#x9;&#x9;c0.646-0.786-10.553,12.802-3.477,24.22c3.319,5.36,10.159,10.124,15.599,11.947c4.062,1.36,10.833,1.737,14,1.737&#xD;&#xA;&#x9;&#x9;&#x9;c2.646,0,7.768-0.541,10.32-0.979c3.021-0.519,6.479-1.629,10.055-3.108C83.081,50.442,87.922,43.904,86.452,37.457z&#xD;&#xA;&#x9;&#x9;&#x9; M39.771,12.212l-2.035-2.039 M53.688,8.212c2,0.333,4.333,1.366,4.333,1.366"/>
17
+ <path id="Muzzle" fill="#FFFFFF" d="M77.146,30.129l-2.881-3.107l-9.113,2.661c0,0.01-0.005,1.619-1.42,2.933&#xD;&#xA;&#x9;&#x9;&#x9;c-0.646,0.6-1.583,1.138-2.948,1.435c-4.371,0.951-11.212-2.277-11.212-2.277l-7.784,3.417c0,0-4.744,0.522-7.244,2.956&#xD;&#xA;&#x9;&#x9;&#x9;c-0.833,0.812-1.417,1.835-1.492,3.123c-0.493,8.398,8.68,13.252,15.083,15.602c1.778,0.652,2.388,0.851,3.472,1.139&#xD;&#xA;&#x9;&#x9;&#x9;c0,0,7.424-0.195,10.181-0.588c3.032-0.432,7.578-1.758,11.154-3.236c9.498-3.933,14.601-10.024,13.129-16.471&#xD;&#xA;&#x9;&#x9;&#x9;C84.597,31.265,77.146,30.129,77.146,30.129z"/>
18
+ </g>
19
+ <g id="L_Eye">
20
+ <path id="Eyeball_1_" fill="#FFFFFF" stroke="#000000" d="M48.638,22.831c2.926,4.48,2.618,9.862-0.682,12.015&#xD;&#xA;&#x9;&#x9;&#x9;c-3.303,2.159-8.352,0.272-11.275-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C40.661,16.465,45.709,18.349,48.638,22.831"/>
21
+ <path id="Pupil_1_" d="M46.85,28.719c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.804-0.878-1.804-1.957&#xD;&#xA;&#x9;&#x9;&#x9;c0-1.08,0.812-1.957,1.804-1.957C46.045,26.762,46.85,27.639,46.85,28.719"/>
22
+ </g>
23
+ <g id="R_Eye">
24
+ <path id="Eyeball" fill="#FFFFFF" stroke="#000000" d="M72.361,19.75c2.925,4.479,3.283,9.419,0.492,11.243&#xD;&#xA;&#x9;&#x9;&#x9;c-2.793,1.825-7.181-0.552-10.104-5.034c-2.933-4.48-3.079-9.134-0.286-10.959C65.25,13.177,69.434,15.268,72.361,19.75"/>
25
+ <path id="Pupil" d="M71.86,26.734c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.804-0.877-1.804-1.956c0-1.08,0.811-1.958,1.804-1.958&#xD;&#xA;&#x9;&#x9;&#x9;C71.058,24.776,71.86,25.654,71.86,26.734"/>
26
+ </g>
27
+ <path id="Nose" fill="#5E4A42" stroke="#000000" d="M60.536,32.138c1.854,0,4.556-0.284,4.696,0.569&#xD;&#xA;&#x9;&#x9;c0.145,0.855-1.709,4.203-2.987,4.345c-1.282,0.142-6.125-2.353-6.194-3.919C55.977,31.567,58.825,32.138,60.536,32.138"/>
28
+ <path id="Whisker_3_" d="M22.75,36.602c-0.023-0.017-2.382-1.76-6.002-3.362c-4.012-1.777-6.263-2.26-6.284-2.265l0.245-1.175&#xD;&#xA;&#x9;&#x9;c0.095,0.02,2.381,0.506,6.525,2.342c3.751,1.661,6.133,3.424,6.232,3.498L22.75,36.602L22.75,36.602z"/>
29
+ <path id="Whisker_2_" d="M16.628,41.254c-2.035,0-5.092-0.276-5.238-0.289l0.107-1.195c0.041,0.004,4.088,0.367,5.887,0.266&#xD;&#xA;&#x9;&#x9;c1.82-0.104,5.412-0.578,5.447-0.583l0.157,1.189c-0.148,0.02-3.67,0.484-5.537,0.591C17.209,41.248,16.929,41.254,16.628,41.254&#xD;&#xA;&#x9;&#x9;L16.628,41.254z"/>
30
+ <path id="Whisker_1_" d="M82.524,35.475L82.33,34.29c0.046-0.007,4.524-0.744,6.761-1.523c2.573-0.894,4.326-2.415,4.345-2.43&#xD;&#xA;&#x9;&#x9;l0.791,0.902c-0.078,0.068-1.948,1.691-4.742,2.661C87.151,34.714,82.711,35.444,82.524,35.475L82.524,35.475z"/>
31
+ <path id="Whisker" d="M91.551,42.979c-0.812,0-1.905-0.035-3.189-0.163c-2.858-0.286-5.584-1.379-5.699-1.426l0.45-1.112&#xD;&#xA;&#x9;&#x9;c0.027,0.011,2.686,1.075,5.368,1.344c2.672,0.267,4.518,0.111,4.535,0.11l0.104,1.195C93.077,42.931,92.508,42.979,91.551,42.979&#xD;&#xA;&#x9;&#x9;L91.551,42.979z"/>
32
+ <path id="Mouth" fill="none" stroke="#231F20" stroke-width="1.2" d="M47.545,39.447c0,0,10.347,3.054,14.217,3.896&#xD;&#xA;&#x9;&#x9;c3.867,0.842,10.851,1.684,10.851,1.684s-7.989,10.245-17.327,7.645C45.947,50.075,46.115,44.187,47.545,39.447z"/>
33
+ </g>
34
+ </g>
35
+ </svg>