squared 0.4.18 → 0.5.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.
data/README.md CHANGED
@@ -1,727 +1,1360 @@
1
- # squared 0.4
1
+ # squared 5.5
2
2
 
3
- * [source](https://github.com/anpham6/squared-ruby)
4
- * [docs](https://squared.readthedocs.io)
3
+ ## Documentation
5
4
 
6
- ## Version Compatibility
5
+ ### HTML
7
6
 
8
- | Date | squared | Min | Max | Git |
9
- | :--------: | ------: | -----: | -----: | -----: |
10
- | 2024-12-07 | 0.1.0 | 2.4.0 | 3.3.6 | 2.39 |
11
- | 2025-01-07 | 0.2.0 | 2.4.0 | 3.4.0 | 2.39 |
12
- | 2025-02-07 | 0.3.0 | 2.4.0 | 3.4.1 | 2.39 |
13
- | 2025-03-06 | 0.4.0 | 2.4.0 | 3.4.2 | 2.39 |
7
+ * [squared](https://squared.readthedocs.io)
8
+ * [E-mc](https://e-mc.readthedocs.io)
14
9
 
15
- The range chart indicates the latest Ruby tested against at the time of release.
10
+ > [!NOTE]
11
+ > Content in the README was migrated into `Read the Docs`. The file is no longer fully maintained.
12
+
13
+ ### README
14
+
15
+ * [squared-express](https://github.com/anpham6/squared-express#readme)
16
+ * [E-mc](https://github.com/anpham6/e-mc#readme)
17
+ * [Pi-r](https://github.com/anpham6/pi-r#readme)
18
+ * [Pi-r2](https://github.com/anpham6/pi-r2#readme)
16
19
 
17
20
  ## Installation
18
21
 
22
+ * NodeJS 18
23
+
24
+ ### NPX
25
+
19
26
  ```sh
20
- gem install squared
27
+ > npm init
28
+ > npm i sqd-cli sqd-serve
29
+
30
+ > npx sqd init
31
+ # OR
32
+ > npx sqd init --public --local-serve # Same as squared-express
33
+
34
+ > npx sqd serve --access-all
35
+ # OR
36
+ > node serve.cjs --access-all
21
37
  ```
22
38
 
23
- ### Optional
39
+ ```sh
40
+ > npm init
41
+ > npm i squared sqd-serve
42
+
43
+ > mkdir dist html
44
+ > cp -r ./node_modules/squared/dist/* ./dist
45
+ > cp ./node_modules/squared/html/* ./html # optional
46
+ > cp ./node_modules/sqd-serve/config/json/* . # yaml
24
47
 
25
- * [Repo](https://source.android.com/docs/setup/reference/repo)
26
- * https://github.com/anpham6/squared-repo
27
- * Python 3.6
28
- * Not compatible with Windows
48
+ > npx serve
49
+ ```
50
+
51
+ ### GitHub
29
52
 
30
53
  ```sh
31
- mkdir -p ~/.bin
32
- PATH="${HOME}/.bin:${PATH}"
33
- curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
34
- chmod a+rx ~/.bin/repo
54
+ > git clone https://github.com/anpham6/squared
55
+ > cd squared
56
+
57
+ > npm i
58
+ > npm run prod
59
+
60
+ > cd ..
61
+
62
+ > git clone https://github.com/anpham6/squared-express
63
+ > cd squared-express
64
+
65
+ > npm i
66
+ > npm run prod
67
+ > npm run deploy # deploy:yaml
68
+
69
+ > cd ../squared
70
+
71
+ > node serve.cjs --access-all # squared.{json,yml}
35
72
  ```
36
73
 
37
- ## Example - Rakefile
74
+ ### Repo
75
+
76
+ * Python 3.6+
77
+ * [Commands](https://source.android.com/docs/setup/reference/repo)
38
78
 
39
- Projects from any accessible folder can be added relative to the parent directory (e.g. *REPO_ROOT*) or absolutely. The same Rakefile can also manage other similarly cloned `Repo` repositories remotely by setting the `REPO_ROOT` environment variable to the location. Missing projects will simply be excluded from the task runner.
79
+ #### Install
40
80
 
41
- ```ruby
42
- require "squared"
81
+ ```sh
82
+ mkdir -p ~/bin/repo
83
+ PATH="${HOME}/bin:${PATH}"
43
84
 
44
- require "squared/workspace"
45
- require "squared/workspace/repo" # Optional
46
- require "squared/workspace/project/node" #
47
- require "squared/workspace/project/python" #
48
- require "squared/workspace/project/ruby" #
49
- require "squared/workspace/project/docker" #
85
+ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
86
+ chmod a+rx ~/bin/repo
50
87
  # OR
51
- require "squared/app" # All workspace related modules
88
+ scripts/repo-install.sh ~/bin
89
+ ```
90
+
91
+ #### Usage
92
+
93
+ ```sh
94
+ mkdir workspaces
95
+ cd workspaces
96
+
97
+ repo init -u https://github.com/anpham6/squared-repo -m latest.xml
98
+ repo sync -j4
52
99
 
53
- # NODE_ENV = production
100
+ cd squared
101
+ npm i
102
+ ```
54
103
 
55
- # REPO_ROOT = /workspaces |
56
- # REPO_HOME = /workspaces/squared | Dir.pwd
57
- # rake = /workspaces/squared/Rakefile | main?
104
+ #### Ruby
105
+
106
+ Workspace management uses [Ruby](https://www.ruby-lang.org/en/documentation/installation) for syncing and building. It is not installed by default on most operating systems.
107
+
108
+ ```sh
109
+ mkdir workspaces
110
+ cd workspaces # REPO_ROOT
111
+
112
+ wget https://unpkg.com/squared/Rakefile
113
+
114
+ rake -T # List tasks
115
+
116
+ # REPO_BUILD={dev,prod}
117
+ # PIPE_FAIL=1
118
+ rake repo:init # nightly
119
+ rake repo:init[latest] # squared
120
+ rake repo:init[0.11.x] # e-mc
58
121
  # OR
59
- # REPO_ROOT = /workspaces | Dir.pwd
60
- # rake = /workspaces/Rakefile |
61
- # REPO_HOME = /workspaces/squared | main: "squared"
62
-
63
- # pathname = /workspaces/pathname
64
- # optparse = /workspaces/optparse
65
- # log = /workspaces/logger
66
- # emc = /workspaces/e-mc
67
- # pir = /workspaces/pi-r
68
- # pir2 = /workspaces/pi-r2
69
- # squared = /workspaces/squared
70
- # cli = /workspaces/squared/publish/sqd-cli
71
- # sqd-serve = /workspaces/squared/publish/sqd-serve
72
- # sqd = /workspaces/squared/sqd
73
-
74
- Workspace::Application
75
- .new(Dir.pwd, main: "squared") # Dir.pwd? (main? is implicitly basename)
76
- .banner("group", "project", styles: ["yellow", "black"], border: "bold") # name | project | path | ref | group? | parent? | version?
77
- .repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "build:prod"], ref: :node) # Repo (optional)
78
- .run("rake install", ref: :ruby)
79
- .depend(false, group: "default")
80
- .clean("rake clean", group: "default")
81
- .clean(["build/"], group: "app")
82
- .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
83
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
84
- "CFLAGS" => "-fPIC -O1"
85
- })
86
- .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", group: "default") # Uses bundler/gem_tasks (without C extensions)
87
- .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "~/.rvm/gems/ruby-3.4.0/gems/logger-1.6.1" }, clean: ["tmp/"]) # autodetect: true | "rvm" | "rbenv" | "asdf" | "bundler"
88
- .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
89
- .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
90
- .add("pi-r2", "pir2", copy: { from: :npm, also: %i[squared express], files: ["LICENSE", ["README.md.ruby", "README.md"]] }) # Uses dist files from NPM package spec
91
- .add("squared", init: 'pnpm', script: ["build:stage1", "build:stage2"], group: "app") do # Use pnpm/yarn/berry for depend + Copy target (main)
92
- # Repo (global)
93
- as(:run, "build:dev", "dev") # npm run build:dev -> npm run dev
94
- as(:run, { "build:dev": "dev", "build:prod": "prod" })
95
-
96
- add("publish/sqd-cli", "cli", exclude: [:git]) # rake cli:build
97
- add("publish/sqd-serve") # rake sqd-serve:build
98
- add("publish/sqd-admin", group: "sqd", exclude: [:base])
99
- # OR
100
- with(exclude: [:base]) { add("publish/*", "packages") } # rake packages:sqd-serve:build
101
- # OR
102
- add(["publish/sqd-cli", "publish/sqd-serve"], true, exclude: [:base]) # rake squared:sqd-serve:build
103
-
104
- # Git
105
- revbuild(include: %w[src/ framework/ types/]) # Synchronous is recommended
106
- end
107
- .add("squared/sqd", exclude: :git, pass: [:node, "checkout", "bump"]) do # Skip initialize(:node) + squared:checkout:* + squared:bump:*
108
- variable_set :script, "build:sqd" # Override detection
109
- variable_set :depend, false
110
- variable_set :clean, ["build/sqd/"]
111
- end
112
- .with(:docker, only: ["build", "compose"]) do
113
- .add("squared", "docker", file: "Dockerfile", context: ".", tag: "latest", registry: "localhost:5000", username: "squared",
114
- args: "--ssh=default",
115
- secrets: ["id=github,env=GITHUB_TOKEN"],
116
- mounts: ["src=.,dst=/project,ro,bind-propagation=rshared"]) do # Docker
117
- series(:clean) do # run | depend | doc | lint | test | copy | clean
118
- File.read(basepath("docker-bake.hcl"))
119
- .scan(/\btags\s+=\s+\["([^"]+)"\]/)
120
- .each { |val| image(:rm, tag: val.first) }
121
- end
122
- end
123
- end
124
- .pass("pull", group: "default") { test? || doc? } # rake pathname:pull | rake optparse:pull
125
- .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
126
- .build(default: "build", parallel: ["pull", "fetch", "rebase", "archive", "clean", /^outdated:/], pass: ["publish"]) do |workspace|
127
- workspace
128
- .enable_aixterm
129
- .style({
130
- banner: ["bright_cyan", "bold", "bright_black!"],
131
- border: "bright_white"
132
- })
133
- end
134
-
135
- # default = /workspaces/ruby/*
136
- # pathname = /workspaces/ruby/pathname
137
- # optparse = /workspaces/ruby/optparse
138
- # logger = /workspaces/ruby/logger
139
- # android = /workspaces/android-docs
140
- # chrome = /workspaces/chrome-docs
141
-
142
- Workspace::Application
143
- .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
144
- .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
145
- pathname: {
146
- run: "rake compile" # rake rb:pathname:build
147
- }
148
- })
149
- .with(:python, editable: false) do # ref=Symbol | group=String
150
- banner([:name, ": ", :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
151
- doc("make html") # rake rb:doc:python
152
- run(false) # rake rb:build:python (disabled)
153
- exclude(%i[base git]) # Project::Git.ref (superclass)
154
- add("android-docs", "android") # rake rb:android:doc
155
- add("chrome-docs", "chrome") # rake rb:chrome:doc
156
- end
157
- .style("inline", "bold")
158
- .build
159
- ```
160
-
161
- **NOTE**: The use of "**ref**" (class name) is only necessary when initializing an empty directory (e.g. *rake repo:init*).
162
-
163
- ### Archive
164
-
165
- ```ruby
166
- # HEADERS={"Authorization":"Bearer RANDOM-TOKEN"} | hash/json
167
- # ZIP_DEPTH=0 | default=1
168
- # TAR_DEPTH=0 | TAR_DEPTH_SQUARED
169
- # UNPACK_FORCE=1 | Remove target directory
170
-
171
- Workspace::Application
172
- .new(main: "squared")
173
- .with(:python) do
174
- add("android-docs", "android", archive: "https://github.com/anpham6/android-docs/archive/refs/tags/v0.3.0.zip")
175
- add("chrome-docs", "chrome", archive: {
176
- uri: "https://github.com/anpham6/chrome-docs/archive/refs/tags/v0.5.0.tar.gz", # URI.open (required)
177
- digest: "e3d55d2004d4770dd663254c9272dc3baad0d57a5bd14ca767de6546cdf14680", # SHA1 | SHA256 | SHA384 | SHA512 | MD5
178
- digest: "rmd160:47b7790a511eed675fec1a3e742845fef058799b", # RMD160
179
- ext: "tar.gz", # zip | tar | tar.gz | tgz | tar.xz | txz | 7z
180
- depth: 1, # nested directories (e.g. --strip-components)
181
- headers: { # URI.open
182
- "Authorization" => "Bearer RANDOM-TOKEN"
183
- }
184
- })
185
- end
186
- .add("squared", release: "https://github.com/anpham6/squared/archive/refs/tags/??") # squared:unpack:zip[v5.4.0,/tmp/squared]
187
- end
188
- ```
189
-
190
- ### Clone
191
-
192
- The task is only active when the project directory is empty or does not exist.
193
-
194
- ```ruby
195
- Workspace::Application
196
- .new(main: "squared")
197
- .git(
198
- "emc": "https://github.com/anpham6/e-mc", # rake emc:clone
199
- "pir": { # rake pir:clone
200
- uri: "https://github.com/anpham6/pi-r", #
201
- options: { #
202
- "origin": "github", # --origin=github
203
- "recurse-submodules": false, # --no-recurse-submodules
204
- "shallow-exclude": ["v0.0.1", "v0.0.2"] # --shallow-exclude=v0.0.1 --shallow-exclude=v0.0.2
122
+ REPO_ROOT=/tmp/123 NODE_INSTALL=pnpm repo:init
123
+ # OR - without Repo
124
+ rake clone:node # master
125
+ rake clone # node + docs
126
+ ```
127
+
128
+ > [!TIP]
129
+ > Use the supplied Rakefile inside the **squared** project folder once the source has been downloaded.
130
+
131
+ #### Docker
132
+
133
+ ```sh
134
+ # NODE_TAG=latest
135
+ # RUBY_VERSION=2.4.0-3.4.0
136
+ # MANIFEST=nightly,prod,latest,android
137
+ # BUILD={dev,prod}
138
+ # DEV={0,1,local}
139
+ # DOCS=any
140
+ # PIPE_FAIL={0,1}
141
+ # PORT=3000
142
+ docker build -t squared --build-arg MANIFEST=prod --build-arg NODE_ENV=production .
143
+ docker build -t node --build-arg NODE_TAG=22 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim .
144
+ NODE=22 docker buildx bake node
145
+ # OR
146
+ docker build -t ruby --build-arg RUBY_TAG=3.4.0 --build-arg NODE_VERSION=22 --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
147
+ RUBY=3.4.0 docker buildx bake ruby
148
+ # OR
149
+ docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=20 -f Dockerfile.nginx .
150
+ NGINX=1.27 docker buildx bake nginx
151
+
152
+ # Express
153
+ docker run -it --name express --rm -p 3000:3000 \
154
+ --mount type=bind,src=${PWD},dst=/workspaces/squared/.config \
155
+ --mount type=bind,src=${PWD}/html,dst=/workspaces/squared/www \
156
+ squared
157
+
158
+ docker run -it --name express --rm -p 443:443 --build-arg PORT=443 squared \
159
+ serve --access-all --https --env=production
160
+
161
+ # Terminal
162
+ docker run -it --name debian squared /bin/bash # irb
163
+ ```
164
+
165
+ ### Browser
166
+
167
+ * Download (squared@version): https://unpkg.com/squared
168
+ * Global JS variable: **squared**
169
+ * ES2018
170
+
171
+ > - https://unpkg.com/squared/dist/squared.min.js
172
+ > - https://unpkg.com/squared/dist/squared.base-dom.min.js
173
+ > - https://unpkg.com/squared/dist/vdom.framework.min.js
174
+
175
+ > * https://unpkg.com/squared/dist/squared.min.js
176
+ > * https://unpkg.com/squared/dist/vdom-lite.framework.min.js
177
+
178
+ ## Usage
179
+
180
+ Library files are in the `/dist` folder. A minimum of **two** files are required to run *squared*.
181
+
182
+ 1. squared
183
+ 2. squared-base - *required: except vdom-lite*
184
+ 3. **squared-svg** - *optional*
185
+ 4. framework (e.g. **android** | **chrome** | vdom | vdom-lite)
186
+ 5. extensions - *optional*
187
+
188
+ Usable combinations: 1-2-4 + 1-2-4-5 + 1-2-3-4-5 + 1-vdom-lite
189
+
190
+ File bundles for common combinations are available in the `/dist/bundles` folder and do not require a call to **setFramework**.
191
+
192
+ > [!WARNING]
193
+ > Libraries in bold are transpiled with **ES2020**.
194
+
195
+ ### Example: android
196
+
197
+ The primary function `parseDocument` can be called on multiple elements and multiple times per session. The application will continuously and progressively build the layout files into a single entity with combined shared resources.
198
+
199
+ * ES2020
200
+
201
+ > [!CAUTION]
202
+ > Using `parseDocumentSync` is not recommended when your page has images or fonts.
203
+
204
+ ```html
205
+ <script src="/dist/squared.min.js"></script>
206
+ <script src="/dist/squared.base.min.js"></script>
207
+ <script src="/dist/squared.svg.min.js"></script>
208
+ <script src="/dist/android.framework.min.js"></script>
209
+ <script>
210
+ squared.settings.targetAPI = 35; // Optional
211
+
212
+ document.addEventListener("DOMContentLoaded", async () => {
213
+ squared.setFramework(android, {/* settings */});
214
+
215
+ await squared.parseDocument(): Node // document.body (default)
216
+ // OR
217
+ await squared.parseDocument(/* HTMLElement */, /* "fragment-id" */, /* ...etc */): Node[]
218
+ // OR
219
+ await squared.parseDocument(
220
+ { // Custom settings do not affect other layouts
221
+ element: document.body,
222
+ projectId: "project-1", // Default is "_"
223
+ resourceQualifier: "land", // "res/*" folder suffix
224
+ /* OR */
225
+ resourceQualifier: {
226
+ suffix: "land", // Used for "true" or "undefined" groups (optional)
227
+ layout: true, // Will copy to "res/layout-land" when "suffix" is defined
228
+ string: undefined, // Will copy to default location "res/values" when "suffix" is undefined
229
+ font: false, // Will not copy anything to "res/font" or "res/font-land"
230
+ image: "hdpi", // Will copy to "res/drawable-hdpi"
231
+ video: "w720dp", // Will copy to "res/raw-w720dp"
232
+ audio: "w720dp", // Same as "video" and treated separately
233
+ animation: "v34", // Will copy to "res/anim-v34"
234
+ menu: "" // Will copy to default location "res/menu"
235
+ /* integer + fraction + array + color + dimension + style + theme = Same as "string" */
236
+ },
237
+ enabledMultiline: false,
238
+ enabledSubstitute: true,
239
+ include: ["android.substitute"], // Automatically removed after finalize
240
+ exclude: ["squared.list", "squared.grid"], // Disabled only during parseDocument
241
+ excludeQuery: [{
242
+ selector: "main > article", // Hide elements
243
+ /* OR */
244
+ resource: squared.base.lib.constant.NODE_RESOURCE.BOX_STYLE, // Exclusions during processing
245
+ procedure: squared.base.lib.constant.NODE_PROCEDURE.OPTIMIZATION,
246
+ section: squared.base.lib.constant.APP_SECTION.DOM_TRAVERSE
247
+ }],
248
+ customizationsBaseAPI: -1,
249
+ observe(mutations, observer, settings) { // Uses MutationObserver
250
+ squared.reset(); // Required when calling "parseDocument" after a File action
251
+ squared.parseDocument(settings).then(() => {
252
+ squared.copyTo("/path/project", { modified: true }).then(response => console.log(response));
253
+ });
254
+ },
255
+ afterCascade(sessionId, node) {/* Restore previous state */},
256
+ beforeRender(layout: LayoutUI) {/* Edit initial values */},
257
+ afterFinalize(node: NodeUI) {/* Edit controller values */}
258
+ },
259
+ { // Only "element" is required
260
+ element: "fragment-1",
261
+ projectId: "project-1", // Implicit once projectId is not "_"
262
+ resourceQualifier: "land",
263
+ pathname: "app/src/main/res/layout-hdpi", // Will not be overridden by resourceQualifier "land"
264
+ filename: "fragment.xml",
265
+ baseLayoutAsFragment: {
266
+ name: "androidx.navigation.fragment.NavHostFragment",
267
+ documentId: "main_content",
268
+ app: {
269
+ navGraph: "@navigation/product_list_graph",
270
+ defaultNavHost: "true"
271
+ }
272
+ },
273
+ beforeCascade(sessionId) {
274
+ document.getElementById("fragment-id").style.display = "block"; // Use inline styles
275
+ }
276
+ }
277
+ );
278
+ await squared.parseDocument({
279
+ element: "fragment-2",
280
+ projectId: "sqd2", // Explicit
281
+ resourceQualifier: "port", // Will not conflict with projectId "project-1"
282
+ enabledFragment: true,
283
+ fragmentableElements: [
284
+ {
285
+ selector: "main", // querySelector
286
+ name: "androidx.navigation.fragment.NavHostFragment",
287
+ filename: "navigation.xml",
288
+ documentId: "main_content"
289
+ },
290
+ "main > article" // Declarative double nested fragments are invalid (querySelectorAll)
291
+ ],
292
+ options: {
293
+ "android.resource.fragment": {
294
+ dynamicNestedFragments: true // FragmentContainerView or FrameLayout as the container (name and tag are ignored)
295
+ }
296
+ }
297
+ });
298
+ // OR - Chromium
299
+ squared.prefetch("css").then(() => squared.parseDocument()); // Cross-origin support
300
+ Promise.all(
301
+ squared.prefetch("css", true), // All stylesheets
302
+ squared.prefetch("css", "./undetected.css", element.shadowRoot),
303
+ squared.prefetch("svg", "http://embedded.example.com/icon.svg", "../images/android.svg")
304
+ )
305
+ .then(() => squared.parseDocument());
306
+
307
+ // Modify attributes
308
+
309
+ const body = squared.findDocumentNode(document.body);
310
+ body.android("layout_width", "match_parent");
311
+ body.lockAttr("android", "layout_width");
312
+
313
+ await squared.close(/* projectId */); // Next call to parseDocument will reset project (optional)
314
+
315
+ squared.kill("30s").then(result => {/* killed when result > 0 */}); // Abort next request in 30 seconds
316
+
317
+ // File actions - implicitly calls "close"
318
+
319
+ await squared.save(/* "project-1" */, /* broadcastId | timeout */); // Uses defaults from settings
320
+ // OR
321
+ await squared.saveAs(/* archive filename */, { projectId: "project-1" });
322
+ await squared.saveAs(/* archive filename */, { timeout: 10 }); // Kills request if not complete in 10 seconds
323
+ await squared.saveAs(/* archive filename */, { throwErrors: true }).catch(err => console.log(err)); // Will cancel partial archive download
324
+ // OR
325
+ await squared.copyTo(/* directory */, {/* options */});
326
+ await squared.copyTo(/* directory */, { modified: true }); // Can be used with observe
327
+ // OR
328
+ await squared.appendTo(/* archive location */, {/* options */});
329
+
330
+ // Other features
331
+
332
+ squared.observe();
333
+ // OR
334
+ await squared.observeSrc(
335
+ "link[rel=stylesheet]", // HTMLElement
336
+ (ev, element) => {
337
+ squared.reset();
338
+ squared.parseDocument().then(() => squared.copyTo("/path/project"));
339
+ },
340
+ { port: 8080, secure: false, action: "reload" /* "hot" */, expires: "1h" } // squared.json: "observe"
341
+ );
342
+
343
+ squared.reset(/* projectId */); // Start new "parseDocument" session (optional)
344
+ });
345
+ </script>
346
+ ```
347
+
348
+ > [!CAUTION]
349
+ > Calling `saveAs` or `copyTo` methods before the images have completely loaded can cause them to be excluded from the generated layout. In these cases you should use the asynchronous `parseDocument` method to set a callback for your commands.
350
+
351
+ ### Example: chrome
352
+
353
+ Used primarly for developing single page layouts but can also bundle assets using query selector syntax. It is adequate for most projects and gives you the ability to develop your application as a module in place.
354
+
355
+ * ES2020
356
+
357
+ ```html
358
+ <script src="/dist/squared.min.js"></script>
359
+ <script src="/dist/squared.base.min.js"></script>
360
+ <script src="/dist/chrome.framework.min.js"></script>
361
+ <script>
362
+ document.addEventListener("DOMContentLoaded", async () => {
363
+ squared.setFramework(chrome, {/* settings */});
364
+
365
+ await squared.save(); // Uses defaults from settings
366
+ // OR
367
+ await squared.saveAs(/* archive filename */, {/* options */});
368
+ // OR
369
+ await squared.copyTo(/* directory */, {/* options */});
370
+ // OR
371
+ await squared.appendTo(/* archive location */, {/* options */});
372
+
373
+ // Observe
374
+ await squared.copyTo(/* directory */, { useOriginalHtmlPage: false, observe: /* Same as Android */ | true /* Auto-reload */}).then(() => squared.observe());
375
+ });
376
+ </script>
377
+ ```
378
+
379
+ ### Example: vdom
380
+
381
+ The most minimal framework possible (*55kb gzipped*) and can be useful when debugging through DevTools. The `lite` version is about half the bundle size and is recommended for most browser applications.
382
+
383
+ * ES2018
384
+
385
+ ```html
386
+ <script src="/dist/squared.min.js"></script>
387
+ <script src="/dist/squared.base-dom.min.js"></script>
388
+ <script src="/dist/vdom.framework.min.js"></script>
389
+ <script>
390
+ document.addEventListener("DOMContentLoaded", async () => {
391
+ squared.setFramework(vdom, {/* settings */});
392
+
393
+ const element = squared.querySelector("body", true /* synchronous */);
394
+ // OR
395
+ const elements = await squared.querySelectorAll("*");
396
+ // OR
397
+ const element = squared.fromElement(document.body, true /* synchronous */);
398
+ // OR
399
+ const elements = await squared.getElementById("content-id").querySelectorAll("*");
400
+ });
401
+ </script>
402
+ ```
403
+
404
+ There are **ES2018** minified versions (\*.min.js) and also **ES2018** non-minified versions.
405
+
406
+ ## User Settings
407
+
408
+ These settings are available in the global variable `squared` to customize your desired output structure. Each framework shares a common set of settings and also a subset of their own settings.
409
+
410
+ ### Example: android
411
+
412
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/android.html)
413
+
414
+ ```javascript
415
+ squared.settings = {
416
+ targetAPI: 35,
417
+ supportRTL: true,
418
+ supportNegativeLeftTop: true,
419
+ preloadImages: true,
420
+ preloadFonts: true,
421
+ preloadLocalFonts: true, // Chromium
422
+ preloadCustomElements: true,
423
+ enabledSVG: true,
424
+ enabledMultiline: true,
425
+ enabledViewModel: true,
426
+ enabledIncludes: false,
427
+ enabledFragment: false,
428
+ enabledSubstitute: false,
429
+ enabledCompose: false,
430
+ dataBindableElements: [], // { selector, attr, expression, namespace?, twoWay? } (see Data Binding section)
431
+ includableElements: [], // { selectorStart, selectorEnd, pathname?, filename?, merge?, viewModel? }
432
+ substitutableElements: [], // { selector, tag, tagChild?, renderChildren?, autoLayout? }
433
+ fragmentableElements: [], // selector | ExtensionFragmentElement
434
+ composableElements: [], // selector or property (see Jetpack Compose section)
435
+ baseLayoutAsFragment: false | "fragment-name" | ["fragment-name", "fragment-tag"] | { selector, pathname?, filename?, name?, tag? }, // ExtensionFragmentElement
436
+ baseLayoutToolsIgnore: "", // Android Studio (e.g. "TooManyViews, HardcodedText")
437
+ fontMeasureAdjust: 0.75, // thicker < 0 | thinner > 0 (data-android-font-measure-adjust)
438
+ lineHeightAdjust: 1.1, // shorter < 1 | taller > 1 (data-android-line-height-adjust)
439
+ preferMaterialDesign: false | "MaterialComponents" | "Material3", // Default is "Material3"
440
+ createDownloadableFonts: true,
441
+ createElementMap: false,
442
+ pierceShadowRoot: true,
443
+ adaptStyleMap: true, // Use rendered values for output
444
+ lockElementSettings: true,
445
+ customizationsBaseAPI: 0, // 0 - All | -1 - None
446
+ customizationsBaseAPI: [0, 33, 34], // Multiple
447
+ removeDeprecatedAttributes: true, // Remove all
448
+ removeDeprecatedAttributes: ["enabled", "singleLine"], // Remove all except "enabled" + "singleLine"
449
+ removeUnusedResourceViewId: false,
450
+ idNamingStyle: "android",
451
+ idNamingStyle: "html", // Use element tagName
452
+ idNamingStyle: {
453
+ "__default__": "html", // Optional
454
+ "DIV": "comments", // HTML is uppercase (comments_1 then comments_2)
455
+ "svg": ["vector", 0], // SVG elements areis lowercase (vector_0 then vector_1)
456
+ "#text": "text", // Plain text
457
+ "::first-letter": "dropcap", // Pseudo element
458
+ "main > section": ["content", 1, 2], // content_1 then content_3
459
+ "form input[type=submit]": function(node) {
460
+ return "submit_" + node.id;
461
+ }
462
+ },
463
+ customizationsOverwritePrivilege: true,
464
+ outputMainFileName: "activity_main.xml",
465
+ outputFragmentFileName: "fragment_main.xml",
466
+ /* Project - parseDocument (first only) */
467
+ resourceQualifier: "", // "land" -> "res/layout-land" | "port" -> "res/layout-port" (appended to every "res" folder)
468
+ resourceSystemColors: {
469
+ "system_accent1_100": "white", // Will be converted to ARGB
470
+ "system_accent1_200": ['#ff0000', 0.75], // opacity
471
+ "system_accent1_300": squared.lib.color.parseColor("#000", 1)
472
+ },
473
+ manifestPackage: "", // OR: RequestData<{ namespace: "android.application.id" }>
474
+ manifestLabelAppName: "android",
475
+ manifestThemeName: "AppTheme",
476
+ manifestParentThemeName: "Theme.AppCompat.Light.NoActionBar",
477
+ manifestActivityName: ".MainActivity",
478
+ outputDocumentEditing: true,
479
+ outputDocumentCSS: [], // CSS properties to be processed at server (e.g. "boxShadow")
480
+ outputDirectory: "app/src/main",
481
+ createManifest: false, // Update AndroidManifest.xml
482
+ createBuildDependencies: false | "ktx" | "baseline-profile" | ["ktx", "baseline-profile"], // Update build.gradle
483
+
484
+ // Not customizable with parseDocument
485
+ builtInExtensions: [
486
+ "squared.accessibility",
487
+ "android.delegate.background",
488
+ "android.delegate.negative-x",
489
+ "android.delegate.positive-x",
490
+ "android.delegate.max-width-height",
491
+ "android.delegate.percent",
492
+ "android.delegate.content",
493
+ "android.delegate.scrollbar",
494
+ "android.delegate.radiogroup",
495
+ "android.delegate.multiline",
496
+ "squared.relative",
497
+ "squared.css-grid",
498
+ "squared.flexbox",
499
+ "squared.table",
500
+ "squared.column",
501
+ "squared.list",
502
+ "squared.grid",
503
+ "squared.sprite",
504
+ "squared.whitespace",
505
+ "android.resource.background",
506
+ "android.resource.svg",
507
+ "android.resource.strings",
508
+ "android.resource.fonts",
509
+ "android.resource.dimens",
510
+ "android.resource.styles",
511
+ "android.resource.data",
512
+
513
+ /* EXCLUDED (breaks layout) */
514
+ "android.resource.includes", // enabledIncludes
515
+ "android.substitute", // enabledSubstitute
516
+ "android.resource.fragment", // enabledFragment
517
+ "jetpack.compose.view" // enabledCompose
518
+ ],
519
+ compressImages: false, // TinyPNG API Key <https://tinypng.com/developers>
520
+ compressImages: "****************", // API key
521
+ compressImages: [{ plugin: "imagemin-pngquant", format: "png", options: { quality: [0.6, 0.8] } }], // v5.5
522
+ convertImages: "", // png | jpeg | webp | gif | bmp
523
+ showAttributes: true,
524
+ showAttributes: {
525
+ "hyphenationFrequency": "full", // Replace all ("android" is the default namespace)
526
+ "android:fontFeatureSettings": null, // Delete all
527
+ "app:menu": [
528
+ "@menu/menu_1", "@menu/menu_2", // Replace with "@menu/menu_2" when value is "@menu/menu_1"
529
+ "@menu/menu_3", null // Delete attribute when value is "@menu/menu_3"
530
+ ],
531
+ /* OR */
532
+ "app:menu": {
533
+ "@menu/menu_1": "@menu/menu_2",
534
+ "@menu/menu_3": null
205
535
  }
536
+ },
537
+ showComments: false | ["boxShadow"] | { self: ["boxShadow"], nextSibling: ["marginBottom"], previousSibling: ["marginTop"], parent: ["position", "top", "left"] }, // TODO in layout.xml
538
+ showComments: { include: { tagName: true | ["button"], attributes: true | ["style"], dataset: false, bounds: true }, self: ["boxShadow", ".className"] },
539
+ showErrorMessages: false,
540
+ convertPixels: "dp", // "sp" | "pt" | "in" | "mm"
541
+ convertLineHeight: "sp", // "dp" | "pt" | "in" | "mm"
542
+ convertEntities: ["numeric"],
543
+ convertEntities: ["codepoints", {/* JSON (last) */}], // https://html.spec.whatwg.org/entities.json
544
+ insertSpaces: 4,
545
+ outputDocumentHandler: "android",
546
+ outputEmptyCopyDirectory: false, // Sub directories within target directory (OR: RequestData<{ emptyDir: false }>)
547
+ outputSummaryModal: false | "path/summary.css" | ".status-4 { color: purple; }",
548
+ outputTasks: {
549
+ "**/drawable/*.xml": { handler: "gulp", task: "minify" }
550
+ },
551
+ outputWatch: {
552
+ "**/drawable/*.png": true,
553
+ "**/drawable/*.jpg": { interval: 1000, expires: "2h" }
554
+ },
555
+ outputArchiveName: "android-xml",
556
+ outputArchiveFormat: "zip", // tar | 7z | gz
557
+ outputArchiveCache: false // Downloadable URL in ResponseData<downloadUrl>
558
+ };
559
+
560
+ // Optional
561
+ squared.settings = {
562
+ resolutionDPI: 160, // 320dpi = 2560x1600
563
+ resolutionScreenWidth: 1280,
564
+ resolutionScreenHeight: 800,
565
+ framesPerSecond: 60,
566
+ useShapeGeometryBox: true, // Bounding box uses native SVG method getBbox
567
+ formatUUID: "8-4-4-4-12", // UUID: 8-4-[12345]3-[89ab]3-12
568
+ formatDictionary: "0123456789abcdef",
569
+ outputConfigName: "sqd.config",
570
+ observePort: 8080,
571
+ observeSecurePort: 8443,
572
+ observeExpires: "1h", // Server defaults will be used
573
+ broadcastPort: 3080,
574
+ broadcastSecurePort: 3443
575
+ };
576
+ ```
577
+
578
+ ### Example: chrome
579
+
580
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/chrome.html)
581
+
582
+ ```javascript
583
+ squared.settings = {
584
+ preloadImages: false,
585
+ preloadFonts: false,
586
+ preloadLocalFonts: false,
587
+ preloadCustomElements: false,
588
+ excludePlainText: true,
589
+ createElementMap: true,
590
+ pierceShadowRoot: true,
591
+ adaptStyleMap: false,
592
+ builtInExtensions: [],
593
+ showErrorMessages: false,
594
+ webSocketPort: 80,
595
+ webSocketSecurePort: 443,
596
+ outputDocumentHandler: "chrome",
597
+ outputEmptyCopyDirectory: false,
598
+ outputSummaryModal: false,
599
+ outputTasks: {
600
+ "*.js": [{ handler: "gulp", task: "minify" }, { handler: "gulp", task: "beautify" }]
601
+ },
602
+ outputWatch: { "*": true },
603
+ outputArchiveName: "chrome-data",
604
+ outputArchiveFormat: "zip",
605
+ outputArchiveCache: false
606
+ };
607
+
608
+ // Optional (Same as Android)
609
+
610
+ ```
611
+
612
+ ### Example: vdom
613
+
614
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/vdom.html)
615
+
616
+ ```javascript
617
+ squared.settings = {
618
+ createElementMap: true,
619
+ pierceShadowRoot: false,
620
+ adaptStyleMap: false,
621
+ builtInExtensions: [],
622
+ showErrorMessages: false
623
+ };
624
+ ```
625
+
626
+ ## Local Storage
627
+
628
+ Custom named user settings per framework can be saved to local storage as JSON and reused across all pages in the same domain. Extensions are configured using the same procedure.
629
+
630
+ ```javascript
631
+ // Save
632
+ squared.setFramework(android, { compressImages: true }, "android-example");
633
+
634
+ // Load
635
+ squared.setFramework(android, "android-example");
636
+ ```
637
+
638
+ ```javascript
639
+ // Save
640
+ await squared.copyTo("/path/project", {/* options will be saved */}, "copy-example", true); // Will overwrite and not merge with previously saved settings
641
+
642
+ // Load
643
+ await squared.copyTo("/path/project", {/* takes precedence */}, "http://localhost:3000/copy-to/base-config.json"); // Object.assign({ base-config.json }, options)
644
+ await squared.copyTo("/path/project", {/* takes precedence */}, "copy-example"); // Object.assign({ copy-example }, options)
645
+
646
+ await squared.copyTo("/path/project", "http://localhost:3000/copy-to/base-config.json"); // options = { base-config.json }
647
+ await squared.copyTo("/path/project", "copy-example"); // options = { copy_example }
648
+ ```
649
+
650
+ ## Public Properties and Methods
651
+
652
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/methods/squared.html)
653
+
654
+ ```javascript
655
+ .settings // See user preferences section
656
+
657
+ setFramework(app: {}, options?: PlainObject, setting?: string, cache?: boolean) // Install application interpreter
658
+ setFramework(app: {}, loadName: string, cache?: boolean) // Load settings from local storage
659
+
660
+ // http - hostname(:port)? | https - hostname:443
661
+ setHostname(value: string /* http(s)://hostname(:port) */) // Use another cors-enabled server for processing files (--cors <origin>)
662
+
663
+ setEndpoint(name: string, value: string) // Set alternate pathname for API v1 functions (ASSETS_COPY | ASSETS_ARCHIVE | LOADER_DATA | THREADS_KILL | WEBSOCKET_OBSERVE)
664
+ setLocalAddress(...values: (string | URL | Location)[]) // Additional hostnames which are interpreted as localhost (e.g. http://127.0.0.1)
665
+
666
+ prefetch(type: "css" | "javascript" | "image" | "svg", all?: boolean, ...targets: unknown[]) // Cross-origin support for CSS
667
+
668
+ parseDocument(...elements: (HTMLElement | string | ElementSettings)[]) // See installation section (Promise)
669
+ parseDocumentSync(...elements: (HTMLElement | string | ElementSettings)[]) // Skips preloadImages and preloadFonts (synchronous)
670
+
671
+ latest(count?: number) // Most recent parseDocument session ids (1 newest / -1 oldest: string, other: string[])
672
+
673
+ auth(token: string) // Set JWT authorization token for all requests
674
+
675
+ save(projectId?: string) // Save current session to a new archive using default settings
676
+ save(projectId?: string, broadcastId?: string)
677
+ save(projectId?: string, timeout?: number)
678
+
679
+ close(projectId?: string) // Close current session
680
+ reset(projectId?: string) // Clear cache and reopen new session
681
+ clear() // Clear all data stored in memory
682
+
683
+ toString() // Current framework loaded
684
+ toString(projectId: string) // await squared.close(projectId) (required)
685
+
686
+ add(...names: (string | Extension | ExtensionRequestObject)[]) // See extension configuration section
687
+ remove(...names: (string | Extension)[]) // Remove extensions by namespace or control
688
+ get(...names: string[]) // Retrieve extensions by namespace
689
+ attr(name: string | Extension, attrName: string, value?: unknown) // Set or get extension options attribute value
690
+ apply(name: string | Extension, options: PlainObject, setting?: string) // See extension configuration section
691
+
692
+ extend(functionMap: {}, framework?: /* 0 - ALL | 1 - vdom | 2 - android | 4 - chrome */) // Add extension functions and properties to Node prototype
693
+
694
+ observe(value?: boolean | MutationObserverInit) // Start after DOM and third-party libraries initialization
695
+ broadcast(callback: BroadcastMessageCallback, options: FileBroadcastOptions | string) // Redirect stdout messages to DevTools console
696
+
697
+ // Promise (Recommended "cache": createElementMap - true)
698
+
699
+ getElementById(value: string, sync?: boolean, cache?: boolean) // sync - false | cache - true (default)
700
+ querySelector(value: string, sync?: boolean, cache?: boolean)
701
+ querySelectorAll(value: string, sync?: boolean, cache?: boolean)
702
+
703
+ fromElement(element: HTMLElement | string, sync?: boolean, cache?: boolean) // sync - false | cache - false (default)
704
+ fromNode(node: Node, sync?: boolean, cache?: boolean)
705
+ findDocumentNode(element: HTMLElement | string /* querySelector | elementId | controlId */, all?: boolean) // Use before saving to modify internal Node attributes
706
+
707
+ observeSrc(element: HTMLElement | string /* querySelector */, callback: WebSocketMessageChange, options?: FileObserveOptions) // Can be used to watch any element with externally hosted files (src/href)
708
+ observeSrc(element: HTMLElement | string, options: FileObserveOptions) // Uses location.reload (reload - true)
709
+ ```
710
+
711
+ Packaging methods will return a Promise and requires a squared-express installation. These features are not supported when the framework is VDOM.
712
+
713
+ ```javascript
714
+ saveAs(filename: string, options?: {}, setting?: string, overwrite?: boolean) // Save current session as a new archive
715
+ saveFiles(filename: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from FileAsset[]
716
+
717
+ // Required (local archives): --disk-read | --unc-read | --access-all (command-line)
718
+
719
+ appendTo(pathname: string, options?: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and current session
720
+ appendFiles(pathname: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and FileAsset[]
721
+
722
+ // Required (all): --disk-write | --unc-write | --access-all (command-line)
723
+
724
+ copyTo(pathname: string | string[], options?: {}, setting?: string, overwrite?: boolean) // Copy current session to local
725
+ copyFiles(pathname: string | string[], options: {}, setting?: string, overwrite?: boolean) // Copy FileAsset[] to local
726
+
727
+ kill(pid: number, timeout?: number) // Use -1 or options.pid (set by system) + seconds
728
+ kill(timeout: string)
729
+ kill() // Terminate previous request
730
+ kill(0) // By username (auth required)
731
+ kill(-1, 10) // Terminate previous request in 10 seconds
732
+ kill(NaN, 10) // Terminate in 10 seconds (next request) ("timeout" is required)
733
+ kill("10s") // Only "s" + "ms" (next request)
734
+ ```
735
+
736
+ ## Extending Node object
737
+
738
+ You can add functions and initial variables to the Node object including overwriting preexisting class definitions per framework. Accessor properties are also supported using the *get/set* object syntax.
739
+
740
+ ```javascript
741
+ squared.extend({
742
+ _id: 1,
743
+ altId: {
744
+ get() {
745
+ return this._id;
746
+ },
747
+ set(value) {
748
+ this._id += value;
749
+ }
750
+ },
751
+ customId: {
752
+ value: 2,
753
+ configurable: false,
754
+ enumerable: false
755
+ },
756
+ addEvent(eventName, callback) {
757
+ this.element.addEventListener(eventName, callback);
206
758
  }
207
- )
208
- .git("squared", "/path/to/squared", options: { local: true }) # Relative paths resolve from workspace root
209
- .git(
210
- {
211
- emc: { uri: "e-mc", options: { "depth": 2 } }, # https://github.com/anpham6/e-mc
212
- pir: "pi-r" # Maps task alias to repository folder
759
+ });
760
+ squared.setFramework(vdom);
761
+
762
+ const body = await squared.fromElement(document.body);
763
+ body.altId = 2; // body.altId: 3
764
+ body.addEvent("click", function (ev) {
765
+ this.classList.toggle("example");
766
+ });
767
+ ```
768
+
769
+ ## Forwarding Request
770
+
771
+ Using another identical remote server to build the project when performing a `saveAs` or `copyTo` request can be achieved by changing only the origin address.
772
+
773
+ ```javascript
774
+ squared.setHostname("http://hostname:8000");
775
+ // OR
776
+ squared.setHostname(); // Reset to window.location (e.g. localhost:3000)
777
+
778
+ await squared.saveAs("chrome.zip"); // Current browser
779
+ // OR
780
+ await squared.copyTo("/path/project"); // Remote server
781
+ ```
782
+
783
+ ## Broadcasting
784
+
785
+ Console messages (stdout) can be sent to the browser console instead through DevTools.
786
+
787
+ ```javascript
788
+ squared.broadcast(result => { console.log(result.value); }, "111-111-111"); // System messages from squared-express
789
+ squared.broadcast(result => { console.log(result.value); }, "222-222-222"); // Messages from "project-1" project
790
+ squared.broadcast(result => { console.log(result.value); }, { socketId: "333-333-333", socketKey: "socket_id" }); // Messages sent from another channel (default is "socketId")
791
+
792
+ await squared.copyTo("/path/project/project-1", {
793
+ projectId: "project-1",
794
+ log: { useColor: true }, // Chromium
795
+ broadcastId: "222-222-222" // Specific use alias for "socketId"
796
+ });
797
+ ```
798
+
799
+ ## Extension Configuration
800
+
801
+ Layout rendering can be customized using extensions as the program was built to be nearly completely modular. Some of the common layouts already have built-in extensions which you can load or unload based on your preference.
802
+
803
+ ```typescript
804
+ // Create an extension
805
+ class Sample extends squared.base.Extension {
806
+ options = {
807
+ attributeName: [];
808
+ };
809
+
810
+ constructor(name, framework = 0, options = {}) { // 0 - ALL | 1 - vdom | 2 - android | 4 - chrome (framework)
811
+ super(name, framework, options);
812
+ }
813
+
814
+ processNode(node: NodeUI) {
815
+ const data = this.project.get(node.element, node.localSettings.projectId);
816
+ if (data) {
817
+ node.each((child, index) => child.element.title = data[index]);
818
+ }
819
+ }
820
+ }
821
+
822
+ // Install an extension
823
+ const sample = new Sample("widget.example.com", 0, {/* Same as configure */});
824
+ squared.add(sample);
825
+ // OR
826
+ squared.add([sample, {/* config */}]);
827
+
828
+ // Configure an extension
829
+ squared.attr("widget.example.com", "attributeName", ["width", "height"]); // typeof is enforced and will only set existing attributes
830
+
831
+ // Add project data
832
+ const ext = squared.get("widget.example.com");
833
+
834
+ ext.project.set(element, await fetch(url?id=1)); // Map interface with optional "projectId" argument
835
+ ext.project.set(element, await fetch(url?id=2), "project-1");
836
+
837
+ const data = ext.project.get(element, "project-2"); // Returns data from default project (id=1)
838
+ ```
839
+
840
+ Some extensions have a few settings which can be configured. The default settings usually achieve the best overall rendering accuracy without noticeably affecting performance.
841
+
842
+ ## ANDROID
843
+
844
+ ### Public Methods
845
+
846
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/methods/android.html)
847
+
848
+ ```javascript
849
+ android.setViewModel(data: {}, sessionId?: string) // Object data for layout bindings
850
+ android.setViewModelByProject(data: {}, projectId?: string)
851
+ android.removeObserver(element: HTMLElement) // Disconnect an observed element from "parseDocument"
852
+ android.addXmlNs(name: string, uri: string) // Add global namespaces for third-party controls
853
+ android.addDependency(group: string, name: string, version?: string, type?: number) // Add application dependency implementation (build.gradle)
854
+ android.addDependencyByProject(projectId: string, group: string, name: string, version?: string, type?: number) // DEPENDENCY_TYPE: 0 - implementation 1 - api 2 - compileOnly 3 - compileOnlyApi 4 - runtimeOnly 5 - testImplementation 8 - androidTestImplementation
855
+ android.customize(build: number, tagNameOrWidget: string, options: {}) // Global attributes applied to specific views
856
+ android.loadCustomizations(name: string) // Load customizations from Local Storage
857
+ android.saveCustomizations(name: string) // Save "customize" data into Local Storage (includes xmlns)
858
+ android.resetCustomizations() // All session customizations are deleted
859
+ android.addFontProvider(authority: string, package: string, certs: string[], webFonts: string | {}) // Add additional Web fonts (Google Fonts already included)
860
+ android.setResolutionByDeviceName(value: string) // Settings prefixed with "resolution" (e.g. Pixel C)
861
+ android.getLocalSettings() // Modify controller styles and parsing rules
862
+ ```
863
+
864
+ ```javascript
865
+ // NOTE: squared.settings.targetAPI is always parsed (Except: customizationsBaseAPI = -1)
866
+
867
+ android.customize(android.lib.constant.BUILD_VERSION.ALL /* 0 */, "Button", {
868
+ android: {
869
+ minWidth: "35px",
870
+ minHeight: "25px"
213
871
  },
214
- base: "https://github.com/anpham6", # Required
215
- repo: ["squared", "android-docs", "chrome-docs"], # https://github.com/anpham6/squared
216
- options: { # Only "repo"
217
- "depth": 1,
218
- "quiet": true
872
+ "_": { // Non-namespaced attributes
873
+ style: "@style/Widget.Material3.Button.TextButton"
219
874
  }
220
- )
221
- .with(:node) do # rake clone:node
222
- add("e-mc", "emc") # https://github.com/anpham6/e-mc
223
- add("pi-r", "pir") # https://github.com/anpham6/pi-r
224
- add("squared") # https://github.com/anpham6/squared
225
- end
226
- .with(:python) do # rake clone:python
227
- add("android-docs")
228
- add("chrome-docs") do
229
- revbuild(include: "source/", exclude: ["source/conf.py"]) # Limit files being watched
230
- end
231
- end
232
- .git("https://github.com/anpham6", ["emc", "pir"]) # Targets any defined project
233
- .git("https://github.com/anpham6", cache: true) # Uses already defined root projects + revbuild
234
- .revbuild # Enables task revbuild (squared.revb)
235
- .revbuild(file: "../build.json") # $ROOT/build.json
236
- .build(parallel: ["clone"]) # rake clone + rake clone:sync
237
- ```
238
-
239
- ### Build
240
-
241
- #### Chain
242
-
243
- There has to be at least one project which uses the ``step`` attribute. Other placement attributes are ignored.
244
-
245
- **NOTE**: Projects can only reference non-global namespaced tasks. (e.g. with ":")
246
-
247
- ```ruby
248
- Workspace::Application
249
- .new
250
- .with(:node) do
251
- add("e-mc", "emc") do
252
- chain "all", "clean", step: 1 # Required
253
- chain "all", "build", step: 2
254
- end
255
- add("pi-r", "pir") do
256
- chain "all", "build", after: "emc:build" # step: 3
257
- end
258
- add("pi-r2", "pir2") do
259
- chain "all", "build", before: "squared" # step: 3
260
- end
261
- add("squared-express", "express") do
262
- chain "all", "clean", with: "emc" # step: 1
263
- chain "all", "build", with: "pir" # step: 3
264
- end
265
- add("squared") do
266
- revbuild(include: %w[src/ framework/ types/]) # Git revision build command (optional)
267
- chain "all", "revbuild", after: "express:build" # step: 4
268
- chain "publish", "bump:patch", "publish:latest", step: 0, sync: true # rake publish -> squared:bump:patch -> squared:publish:latest
269
- end
270
- end
271
- .with(:python) do
272
- doc("make html")
273
- add("android-docs") do
274
- chain "all", "doc", with: "squared", after: "squared" # step: 4
275
- end
276
- add("chrome-docs") do
277
- chain "all", "doc", with: "squared", before: "squared:revbuild" # Same
278
- end
279
- end
280
- .chain "all", "status", with: "squared", after: "android-docs" # Global tasks (e.g. without ":")
281
- .build
875
+ });
876
+
877
+ android.customize(android.lib.constant.BUILD_VERSION.KITKAT /* 19 */, "svg", {
878
+ android: {
879
+ "[src]": "app:srcCompat" // Change namespace to "app"
880
+ }
881
+ });
882
+
883
+ // Local Storage
884
+ android.saveCustomizations("customize-example"); // Save at least once in one layout
885
+
886
+ android.loadCustomizations("customize-example"); // Load in any other layout
282
887
  ```
283
888
 
284
- ```sh
285
- rake all # all[1-3-4]
286
- rake all:print
287
- ```
288
-
289
- Threaded is the default when there are two or more tasks. Using ``with`` and either **before** or **after** will create a synchronous group.
290
-
291
- * Step 1: emc:clean + express:clean (thread)
292
- * Step 2: emc:build (sync)
293
- * Step 3: pir:build + express:build + pir2:build (thread)
294
- * Step 4: chrome-docs:doc + squared:revbuild + android-docs:doc + status (sync)
295
-
296
- #### Graph
297
-
298
- ```ruby
299
- Workspace::Application
300
- .new(main: "squared")
301
- .graph(["depend"], ref: :git) # Optional
302
- .with(:python) do
303
- doc(windows? ? ".\make.bat html" : "make html") # rake android-docs:doc | rake doc:python
304
- add("android-docs", "android", venv: "/home/user/.venv") # rake android-docs:depend
305
- add("chrome-docs", "chrome", graph: "android", venv: %w[.venv --clear]) do # /workspaces/chrome-docs/.venv
306
- variable_set :dependindex, 1 # Use Poetry for dependencies (optional)
307
- end
308
- end
309
- .with(:node) do
310
- graph(["build", "copy"], on: { # Overrides "git"
311
- first: proc { puts "1" },
312
- last: proc { puts "2" }
313
- })
314
- script("build:dev") # npm run build:dev
315
- # OR
316
- run([nil, "build:dev", { "PATH" => "~/.bin" }, "--workspace", "--silent"]) # PATH="~/.bin" npm run build:dev --workspace -- --silent
317
- # OR
318
- run({ # Same
319
- script: "build:dev", #
320
- env: { "PATH" => "~/.bin" }, #
321
- opts: "--workspace", #
322
- args: "--silent" #
323
- })
324
-
325
- add("e-mc", "emc") do
326
- first("build", "emc:clean", "emc:depend") # rake emc:clean && rake emc:depend && rake emc:build && echo "123"
327
- last("build", out: "123") { |out: nil| puts out } #
328
- error("build") { |err: nil| log.debug err } #
329
- end
330
- add("pi-r", "pir", graph: "emc", first: {
331
- build: proc { puts self.name } # puts "pir"
332
- })
333
- add("squared-express", "express", graph: "pir")
334
- add("squared", graph: ["chrome", "express"]) do
335
- first("git:ls-files") { puts "1" } # skipped
336
- first("git:ls-files", override: true) { puts "2" } # puts "2"
337
- last("git:ls-files") { puts workspace.root } # puts "/workspaces"
338
- end
339
- end
340
- .with(:ruby) do
341
- run("gem build") # gem build
342
- # OR
343
- run("gem build", on: { first: -> { p "2" }, last: -> { p "4" } }) do # run | depend | graph | clean | doc | lint | test
344
- p "1"
345
- end
346
- # OR
347
- run(on: { first: -> { p "pass" }, last: -> { p "pass" } }) do
348
- p "1"
349
- end
350
- # OR
351
- run(["gem build", "--force", { "RUBY_VERSION" => "3.4.0" }]) # RUBY_VERSION="3.4.0" gem build --force
352
- # OR
353
- run({ #
354
- command: "gem build", # RUBY_VERSION="3.4.0" gem build --silent --force
355
- opts: "--force", # composable
356
- env: { "PATH" => "~/.bin" }, #
357
- args: { silent: true } #
358
- })
359
- # OR
360
- run(["gem pristine", ["gem build", "gem cleanup"], nil, "--debug"]) # gem pristine --debug && gem build --debug && gem cleanup --debug
361
- #
362
- # All commands are either Array or Hash
363
- #
364
- run([ # PATH="~/.bin" GEM_HOME="~/.gems/ruby-3.4.0" (merged)
365
- ["gem pristine", "--all", { "PATH" => "~/.bin" }, "--silent"], # gem pristine --silent --all
366
- ["gem build", { strict: true }, { "GEM_HOME" => "~/.gems/ruby-3.4.0" }] # gem build --strict
367
- ]) #
368
- # OR
369
- run([ # Same
370
- { #
371
- env: { "PATH" => "~/.bin" }, #
372
- command: "gem pristine", #
373
- opts: "--all", args: "--silent" #
374
- }, #
375
- { #
376
- env: { "GEM_HOME" => "~/.gems/ruby-3.4.0" }, #
377
- command: "gem build", #
378
- opts: { strict: true } #
379
- } #
380
- ])
381
-
382
- add("pathname", test: ["rake test", { jobs: ENV["RAKE_JOBS"] }]) # rake test --jobs 4
383
- add("fileutils", graph: "pathname")
384
- add("optparse", run: "gem build", env: { "PATH" => "~/.bin" }, opts: "-v") # PATH="~/.bin" gem build -v
385
- add("rake", graph: ["fileutils", "optparse"])
386
- banner(command: false) # Always hide banner
387
- end
388
- .build
889
+ ```javascript
890
+ android.addXmlNs("tools", "http://schemas.android.com/tools");
891
+
892
+ android.customize(16 /* Jelly Bean */, "ImageView", {
893
+ tools: {
894
+ ignore: "ContentDescription",
895
+ targetApi: "16"
896
+ }
897
+ });
389
898
  ```
390
899
 
391
- ```sh
392
- rake pir:graph # emc + pir
393
- rake express:graph # emc + pir + express
394
- rake chrome:graph # android + chrome
395
- rake graph:python # same
396
- rake squared:graph # android + chrome + emc + pir + express + squared
397
- rake graph:node # same
398
- rake rake:graph # pathname + fileutils + optparse + rake
399
- rake graph:ruby # same
400
- rake graph # graph:node + graph:ruby
900
+ ### Static Methods
901
+
902
+ Project resources can include additional values that are required during compilation. TypeScript definitions are available in the `types/android` directory.
401
903
 
402
- rake squared:graph:run[express,pir] # emc + pir + express + squared
403
- rake squared:graph:run[node,-emc] # pir + express + squared
904
+ ```javascript
905
+ squared.parseDocument().then(node => {
906
+ const resourceId = node.localSettings.resourceId;
907
+ android.base.Resource.addString(resourceId, value, /* name */);
908
+ android.base.Resource.addArray(resourceId, name, items);
909
+ android.base.Resource.addColor(resourceId, color);
910
+ android.base.Resource.addDimen(resourceId, name, value);
911
+ android.base.Resource.addTheme(resourceId, theme);
912
+ squared.save();
913
+ });
404
914
  ```
405
915
 
406
- ### Tasks
916
+ ### Data Binding
917
+
918
+ View model data can be applied to most HTML elements using the dataset attribute. Different view models can be used for every `parseDocument` session.
919
+
920
+ Leaving the `sessionId` empty uses the default view model which is searched last for all projects when attempting a bind.
921
+
922
+ ```javascript
923
+ // NOTE: latest(undefined = 1): string (1: most recent sessionId | -1: first sessionId)
924
+
925
+ squared.parseDocument("id-1", "id-2", "id-3").then(nodes => {
926
+ const sessions = squared.latest(2); // ["00001", "00002", "00003"] => ["00002", "00003"]
927
+ android.setViewModel(
928
+ {
929
+ import: ["java.util.Map", "java.util.List"],
930
+ variable: [
931
+ { name: "user", type: "com.example.User" },
932
+ { name: "list", type: "List&lt;String>" },
933
+ { name: "map", type: "Map&lt;String, String>" },
934
+ { name: "index", type: "int" },
935
+ { name: "key", type: "String" }
936
+ ]
937
+ },
938
+ sessions[0] // nodes[1].sessionId
939
+ );
940
+ android.setViewModel(
941
+ {
942
+ import: ["java.util.Map"],
943
+ variable: [
944
+ { name: "map", type: "Map&lt;String, String>" }
945
+ ]
946
+ },
947
+ sessions[1] // nodes[2].sessionId
948
+ );
949
+ });
950
+
951
+ squared.parseDocument({
952
+ element: "main",
953
+ enabledViewModel: true,
954
+ dataBindableElements: [
955
+ {
956
+ selector: "#first_name",
957
+ namespace: "android", // "android" is default
958
+ attr: "text",
959
+ expression: "user.firstName"
960
+ },
961
+ {
962
+ selector: "#last_name",
963
+ attr: "text",
964
+ expression: "user.lastName"
965
+ },
966
+ {
967
+ selector: "#remember_me",
968
+ attr: "checked",
969
+ expression: "user.rememberMe",
970
+ twoWay: true
971
+ }
972
+ ],
973
+ data: {
974
+ viewModel: {
975
+ import: ["java.util.Map"],
976
+ variable: [
977
+ { name: "map", type: "Map&lt;String, String>" }
978
+ ]
979
+ }
980
+ }
981
+ });
407
982
 
408
- ```ruby
409
- Workspace::Series.batch(:ruby, :node, {
410
- stage: %i[graph test],
411
- reset: %i[stash pull]
412
- })
983
+ squared.save();
984
+ ```
985
+
986
+ Inlining is also supported and might be more convenient for simple layouts. JavaScript is recommended when you are calling `parseDocument` multiple times.
413
987
 
414
- Workspace::Series.rename("depend", "install")
988
+ ```
989
+ data-viewmodel-{namespace}-{attribute} -> data-viewmodel-android-text
415
990
  ```
416
991
 
417
- ## Usage
992
+ These two additional output parameters are required when using the "**data-viewmodel**" prefix.
418
993
 
419
- ```sh
420
- rake -T # List tasks
421
- rake # rake status (usually "build")
994
+ ```html
995
+ <div id="main">
996
+ <label>Name:</label>
997
+ <input id="first_name" type="text" data-viewmodel-android-text="user.firstName" />
998
+ <input id="last_name" type="text" data-viewmodel-android-text="user.lastName" />
999
+ <input id="remember_me" type="checkbox" data-viewmodel-android-checked="=user.rememberMe" /> <!-- "=" for two-way binding -->
1000
+ </div>
1001
+ ```
422
1002
 
423
- # GIT_OPTIONS=rebase
424
- rake pull # All except "default" + "app"
425
- rake pull:ruby # pathname + optparse + logger
426
- rake pull:default # pathname + optparse
427
- rake pull:app # squared
428
- rake pull:node # emc + pir + squared
1003
+ ```xml
1004
+ <layout>
1005
+ <data>
1006
+ <import type="java.util.Map" />
1007
+ <import type="java.util.List" />
1008
+ <variable name="user" type="com.example.User" />
1009
+ <variable name="list" type="List&lt;String&gt;" />
1010
+ <variable name="map" type="Map&lt;String, String&gt;" />
1011
+ <variable name="index" type="int" />
1012
+ <variable name="key" type="String" />
1013
+ </data>
1014
+ <LinearLayout android:id="@+id/main">
1015
+ <TextView android:text="Name:" />
1016
+ <EditText
1017
+ android:id="@+id/first_name"
1018
+ android:inputType="text"
1019
+ android:text="@{user.firstName}" />
1020
+ <EditText
1021
+ android:id="@+id/last_name"
1022
+ android:inputType="text"
1023
+ android:text="@{user.lastName}" />
1024
+ <CheckBox
1025
+ android:id="@+id/remember_me"
1026
+ android:checked="@={user.rememberMe}" />
1027
+ </LinearLayout>
1028
+ </layout>
1029
+ ```
429
1030
 
430
- rake build # All except "android"
431
- rake doc # optparse + android
432
- rake depend # All except "default"
1031
+ ### Layout Includes / Merge Tag
433
1032
 
434
- rake build:ruby # rake compile + rake install + rake install
1033
+ Some applications can benefit from using includes or merge tags to share common templates. Nested includes is supported.
435
1034
 
436
- rake clean # All except "default" + "app"
437
- rake clean:ruby # rake clean + rake clean + ["tmp/"]
438
- rake clean:default # rake clean + rake clean + skip
439
- rake clean:app # none + skip + ["build/"]
440
- rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
1035
+ ```html
1036
+ <div>
1037
+ <div id="item1">Item 1</div>
1038
+ <div id="item2" data-android-include-start="true" data-android-include-merge="true" data-pathname-android="app/src/main/res/layout-land" data-filename-android="filename1.xml">Item 2</div>
1039
+ <div id="item3">Item 3</div>
1040
+ <div id="item4" data-android-include-end="true">Item 4</div>
1041
+ <div id="item5" data-android-include="filename2" data-android-include-end="true" data-android-include-viewmodel="exampleData">Item 5</div> <!-- viewModel -->
1042
+ </div>
1043
+ ```
441
1044
 
442
- rake squared:run[#] # List scripts (node)
443
- rake squared:rake[#] # List tasks (ruby)
1045
+ ```javascript
1046
+ android.setViewModelByProject({ variable: [{ name: "exampleData", type: "com.example.ExampleData" }] }, "project-1"); // Default is "_"
1047
+
1048
+ squared.parseDocument({
1049
+ element: document.body,
1050
+ projectId: "project-1", // Affects all layouts in same project
1051
+ enabledIncludes: true,
1052
+ includableElements: [
1053
+ {
1054
+ selectorStart: "#item2",
1055
+ selectorEnd: "#item4",
1056
+ pathname: "app/src/main/res/layout-land",
1057
+ filename: "filename1.xml",
1058
+ merge: true // Multiple elements will auto-merge
1059
+ },
1060
+ {
1061
+ selectorStart: "#item5",
1062
+ selectorEnd: "#item5",
1063
+ filename: "filename2",
1064
+ viewModel: "exampleData" // One element only (merge=false)
1065
+ }
1066
+ ]
1067
+ });
1068
+ ```
1069
+ > [!NOTE]
1070
+ > By sessionId has precedence when associating a view model.
1071
+
1072
+ ```xml
1073
+ <LinearLayout>
1074
+ <TextView>Item 1</TextView>
1075
+ <include layout="@layout/filename1" />
1076
+ <include layout="@layout/filename2" app:exampleData="@{exampleData}" />
1077
+ </LinearLayout>
1078
+ <!-- res/layout/activity_main.xml -->
1079
+
1080
+ <merge>
1081
+ <TextView>Item 2</TextView>
1082
+ <TextView>Item 3</TextView>
1083
+ <TextView>Item 4</TextView>
1084
+ </merge>
1085
+ <!-- res/layout-land/filename1.xml -->
1086
+
1087
+ <layout>
1088
+ <data>
1089
+ <variable name="exampleData" type="com.example.ExampleData" />
1090
+ </data>
1091
+ <TextView>Item 5</TextView>
1092
+ </layout>
1093
+ <!-- res/layout/filename2.xml -->
444
1094
  ```
445
1095
 
446
- ```sh
447
- rake build:app # squared + cli + sqd-serve
448
- rake squared:build:workspace # cli + sqd-serve
449
- rake pull:sqd # sqd-admin
450
- rake squared:pull:workspace # sqd-serve + sqd-admin
451
- rake squared:outdated:workspace # cli + sqd-serve + sqd-admin
452
- ```
453
-
454
- ## Methods
455
-
456
- Task:
457
-
458
- * run
459
- * script
460
- * depend
461
- * archive
462
- * graph
463
- * doc
464
- * lint
465
- * test
466
- * clean
467
-
468
- Non-task:
469
-
470
- * log
471
- * exclude
472
-
473
- ## Styles
474
-
475
- * banner
476
- * border
477
- * header
478
- * active
479
- * inline
480
- * subject
481
- * border
482
- * warn
483
- * caution
484
- * current
485
- * latest
486
- * extra
487
- * major
488
- * red
489
- * yellow
490
- * green
491
-
492
- ## Git
493
-
494
- Most project classes will inherit from `Git` which enables these tasks:
495
-
496
- | Task | Git | Command |
497
- | :--------- | :--------------- | :-------------------------------------------- |
498
- | branch | branch | create set delete move copy list edit current |
499
- | checkout | checkout | commit branch track detach path |
500
- | commit | commit | add all amend amend-orig fixup |
501
- | diff | diff | head cached branch files between contain |
502
- | fetch | fetch | origin remote all |
503
- | files | ls-files | cached modified deleted others |
504
- | git | | add blame clean mv rm revert status |
505
- | merge | merge | commit no-commit send |
506
- | pull | pull | origin remote all |
507
- | rebase | rebase | branch onto send |
508
- | refs | ls-remote --refs | heads tags remote |
509
- | reset | reset | commit index patch mode |
510
- | restore | restore | staged worktree |
511
- | rev | rev | commit output |
512
- | show | show | format oneline |
513
- | stash | stash | push pop apply branch drop clear list all |
514
- | submodule | submodule | status update branch url sync |
515
- | switch | switch | branch create detach |
516
- | tag | tag | add sign delete list |
517
-
518
- You can disable all of them at once using the `exclude` property.
519
-
520
- ```ruby
521
- Workspace::Application.exclude('autostash', 'rebase')
522
-
523
- Workspace::Application
524
- .new
525
- .add("squared", exclude: :git)
526
- ```
527
-
528
- You can disable one or more of them using the `pass` property as a *string*.
529
-
530
- ```ruby
531
- Workspace::Application
532
- .new
533
- .add("squared", pass: ["pull"], ref: :node)
534
- .pass("pull", ref: :node) { read_packagemanager(:private) }
535
- ```
536
-
537
- ### Commit Hash
538
-
539
- Commands which use commit hashes are parsed using a ":" prefix as to not be confused for an option.
1096
+ The attributes "**data-android-include-start**" and "**data-android-include-end**" can only be applied to elements which share the same parent container. See `/demos/gradient.html` for usage instructions.
540
1097
 
541
- ```sh
542
- rake squared:log:view[:af012345] # git log af012345
543
- rake squared:log:view[#{af012345}] # deprecated
544
- rake squared:log:view[H1,HEAD^5,all,lib,./H12345] # git log --all @~1 @^5 -- 'lib' 'H12345'
1098
+ > [!TIP]
1099
+ > "**data-pathname-android**" AND "**data-filename-android**" can also be used with any `parseDocument` base element.
1100
+
1101
+ ### Redirecting Output Location
1102
+
1103
+ Sometimes it is necessary to extract elements and append them into other containers for it to look identical on the Android device. Redirection will fail if the *target location* is not a block/container element.
1104
+
1105
+ ```html
1106
+ <div>
1107
+ <span>Item 1</span>
1108
+ <span data-android-target="location">Item 2</span>
1109
+ <span data-android-target="location" data-android-target-index="1">Item 3</span>
1110
+ <div>
1111
+ <ul id="location">
1112
+ <li>Item 4</li>
1113
+ <li>Item 5</li>
1114
+ <!-- span -->
1115
+ </ul>
1116
+ ```
1117
+
1118
+ ```xml
1119
+ <LinearLayout>
1120
+ <TextView>Item 1</TextView>
1121
+ </LinearLayout>
1122
+ <LinearLayout>
1123
+ <TextView>Item 4</TextView>
1124
+ <TextView>Item 3</TextView>
1125
+ <TextView>Item 5</TextView>
1126
+ <TextView>Item 2</TextView>
1127
+ </LinearLayout>
545
1128
  ```
546
1129
 
547
- ## Environment
1130
+ Using `target` into a ConstraintLayout or RelativeLayout container will not include automatic positioning.
548
1131
 
549
- ### Path
1132
+ ### Custom Attributes
550
1133
 
551
- All project binary programs can have their executable path set to a non-global alias.
1134
+ System or extension generated attributes can be overridden preceding finalization. They will only be visible on the declared framework.
552
1135
 
553
- ```ruby
554
- Common::PATH.update({
555
- GIT: "/usr/bin/git", # PATH_GIT=/usr/bin/git
556
- TAR: "/opt/archivers/tar", # PATH_TAR=/opt/archivers/tar
557
- UNZIP: "/opt/archivers/unzip",
558
- GEM: "~/.rvm/gems/ruby-3.4.0/bin/gem",
559
- BUNDLE: "~/.rvm/gems/ruby-3.4.0/bin/bundle",
560
- RAKE: "~/.rvm/gems/ruby-3.4.0/bin/rake",
561
- NPM: "/opt/node/v22.0.0/bin/npm",
562
- PYTHON: "#{ENV["PYTHONPATH"]}/bin/python"
563
- })
1136
+ ```
1137
+ data-android-attr-{namespace}? -> Default is "android"
564
1138
  ```
565
1139
 
566
- ### Build
1140
+ ```html
1141
+ <div id="customId"
1142
+ data-android-attr="layout_width::match_parent;layout_height::match_parent"
1143
+ data-android-attr-app="layout_scrollFlags::scroll|exitUntilCollapsed">
1144
+ </div>
1145
+ ```
567
1146
 
568
- ```ruby
569
- Workspace::Application
570
- .new
571
- .add("squared", run: "gcc a.c -o a.o", opts: { __debug__: { g: true, O2: true, c: nil }, c: true, j: 4 }) # gcc a.c -o a.o -c -j4
1147
+ ```xml
1148
+ <LinearLayout
1149
+ android:id="@+id/customId"
1150
+ android:layout_width="match_parent"
1151
+ android:layout_height="match_parent"
1152
+ app:layout_scrollFlags="scroll|exitUntilCollapsed" />
572
1153
  ```
573
1154
 
574
- ```sh
575
- BUILD_TYPE # global
576
- ${PROG}_COLOR=0 # --no-color (e.g. GIT_COLOR)
1155
+ ```javascript
1156
+ const node = squared.findDocumentNode("customId"); // querySelector is supported
1157
+ node.android("layout_width", "match_parent");
1158
+ node.android("layout_height", "match_parent");
1159
+ node.app("layout_scrollFlags", "scroll|exitUntilCollapsed");
1160
+ ```
1161
+
1162
+ ### SVG animations
577
1163
 
578
- # :env :run :args :opts :type
579
- # LD_LIBRARY_PATH="path/to/lib" CFLAGS="-Wall" gcc a.c -o a.o -g -O2
580
- BUILD_${NAME} # gcc a.c -o a.o
581
- BUILD_${NAME}_OPTS # -g
582
- BUILD_${NAME}_ENV # {"LD_LIBRARY_PATH":"path/to/lib","CFLAGS":"-Wall"} (hash/json)
583
- BUILD_${NAME}_TYPE # debug
1164
+ Only the XML based layout and resource files can be viewed on the Android device/emulator without any Java/Kotlin backend code. To play animations in the emulator you also have to `start` the animation in *MainActivity.java*.
584
1165
 
585
- # :env :script :opts :args
586
- # NODE_ENV="production" NO_COLOR="1" npm run build:dev --loglevel=error --workspaces=false -- --quiet
587
- BUILD_${NAME} # build:dev
588
- BUILD_${NAME}_OPTS # --loglevel=error --workspaces=false
589
- BUILD_${NAME}_ENV # {"NODE_ENV":"production","NO_COLOR":"1"} (hash/json)
590
- BUILD_${NAME}_DEV # pattern,0,1 (:dev)
591
- BUILD_${NAME}_PROD # pattern,0,1 (:prod)
592
- ${REF}_${NAME}_OPTS # --quiet (e.g. NODE_SQUARED_OPTS)
1166
+ ```java
1167
+ import android.graphics.drawable.Animatable;
593
1168
 
594
- BUILD_${NAME}=0 # skip project
595
- BUILD_${NAME}_VERSION=0.1.0 # publish + detection
1169
+ android.widget.ImageView imageView1 = findViewById(R.id.imageview_1);
1170
+ if (imageView1 != null) {
1171
+ Animatable animatable = (Animatable) imageView1.getDrawable();
1172
+ animatable.start();
1173
+ }
1174
+ ```
596
1175
 
597
- BANNER=0 # hide banner
598
- BANNER_${NAME}=0 #
1176
+ ### Jetpack Compose
599
1177
 
600
- REVBUILD_FORCE=1 # Rebuild all targets
601
- REVBUILD_FORCE_${NAME}=1 # Rebuild project
1178
+ Most mobile applications do not have a deeply nested hierarchy and are generally better to implement using declarative programming.
602
1179
 
603
- PREREQS_${NAME}=build,copy # Class method name to invoke
604
- PREREQS_${REF}=depend # e.g. Node
1180
+ ```javascript
1181
+ squared.settings.composableElements = ["main", "#content", "--boxShadow", "--height=300px", {
1182
+ selector: "main",
1183
+ android: {
1184
+ layout_height: "match_parent"
1185
+ },
1186
+ tools: {
1187
+ composableName: "com.example.compose.Preview"
1188
+ }
1189
+ }];
1190
+ squared.settings.createBuildDependencies = true; // Optional
605
1191
  ```
606
1192
 
607
- ### Graph
1193
+ You can also do it using the "**android.substitute**" extension directly inside the HTML element.
608
1194
 
609
- ```sh
610
- GRAPH_${NAME} # depend,build => squared:depend + squared:build
611
- GRAPH_${NAME}_PASS # -emc,pir,express => pir + express
1195
+ ```javascript
1196
+ // android.substitute is only used here to demonstrate using extensions
1197
+
1198
+ squared.add(["android.substitute", {
1199
+ element: {
1200
+ content: { android: { layout_width: "match_parent" } }
1201
+ }
1202
+ }]);
1203
+
1204
+ const items = squared.attr("android.substitute", "viewAttributes");
1205
+ items.push("hint", "buttonTint");
1206
+ /* OR */
1207
+ squared.attr("android.substitute", "viewAttributes", items.concat(["hint", "buttonTint"])); // Attributes to preserve (default is "android.view.View")
1208
+ squared.attr("android.substitute", "attributeMapping", { "android:src": "app:srcCompat", "icon": "navigationIcon" /* android */});
1209
+
1210
+ squared.parseDocument({
1211
+ element: document.body,
1212
+ substitutableElements: [{
1213
+ selector: "#content",
1214
+ tag: "androidx.compose.ui.platform.ComposeView",
1215
+ renderChildren: false
1216
+ }],
1217
+
1218
+ // Some extensions have convenience properties
1219
+ enabledSubstitute: true,
1220
+ /* OR */
1221
+ include: ["android.substitute"]
1222
+ });
612
1223
  ```
613
1224
 
614
- ### Logger
1225
+ ```html
1226
+ <body>
1227
+ <header style="height: 100px"></header>
1228
+ <main id="content"
1229
+ data-use="android.substitute"
1230
+ data-android-substitute-tag="androidx.compose.ui.platform.ComposeView"
1231
+ style="height: 300px; box-shadow: 10px 5px 5px black;">
1232
+ <!-- Interior elements are not rendered -->
1233
+ </main>
1234
+ <footer style="height: 80px"></footer>
1235
+ </body>
1236
+ ```
615
1237
 
616
- These global options also can target the project suffix `${NAME}`. (e.g. LOG_FILE_EMC)
1238
+ Compose will remove child elements by default. You can preserve them by explictly using the renderChildren property. (data-android-substitute-render-children="true")
617
1239
 
618
- ```sh
619
- LOG_FILE # %Y-%m-%d.log
620
- # OR
621
- LOG_AUTO # year,y,month,m,day,d,1
622
- # Optional
623
- LOG_DIR # exist?
624
- LOG_LEVEL # See gem "logger"
1240
+ ```html
1241
+ <div id="fragment"
1242
+ data-use="android.substitute"
1243
+ data-android-substitute-tag="androidx.fragment.app.FragmentContainerView"
1244
+ data-android-substitute-render-children="false"
1245
+ data-android-attr="name::com.github.fragment;tag::example">
1246
+ <!-- Interior elements are not rendered -->
1247
+ </div>
1248
+ ```
1249
+
1250
+ You can also use "**android.substitute**" to create fragments within a layout similar to Compose.
1251
+
1252
+ Usually you do not render child elements when using Compose View. There are some cases where it can be used effectively to reproduce the desired layout.
1253
+
1254
+ ```javascript
1255
+ squared.parseDocument({
1256
+ element: document.body,
1257
+ include: ["android.substitute"], // OR: settings.enabledSubstitute
1258
+ substitutableElements: [{
1259
+ selector: "#navigation",
1260
+ tag: "com.google.android.material.tabs.TabLayout",
1261
+ tagChild: "com.google.android.material.tabs.TabItem",
1262
+ tagChildAttr: {
1263
+ android: {
1264
+ layout_height: "match_parent"
1265
+ }
1266
+ },
1267
+ renderChildren: true,
1268
+ autoLayout: true
1269
+ }]
1270
+ });
625
1271
  ```
626
1272
 
627
- ### Git
1273
+ ```html
1274
+ <ul id="navigation"
1275
+ data-use-android="android.substitute"
1276
+ data-android-attr="layout_height::match_parent"
1277
+ data-android-substitute-tag="com.google.android.material.tabs.TabLayout"
1278
+ data-android-substitute-tag-child="com.google.android.material.tabs.TabItem"
1279
+ data-android-substitute-tag-child-attr="layout_height::match_parent"
1280
+ data-android-substitute-auto-layout="true">
1281
+ <li>TAB 1</li>
1282
+ <li>TAB 2</li>
1283
+ <li>TAB 3</li>
1284
+ </ul>
1285
+ ```
628
1286
 
629
- * Version: 2.50
1287
+ ```xml
1288
+ <com.google.android.material.tabs.TabLayout
1289
+ android:id="@+id/navigation"
1290
+ android:layout_height="match_parent"
1291
+ android:layout_width="wrap_content">
1292
+ <com.google.android.material.tabs.TabItem
1293
+ android:layout_height="match_parent"
1294
+ android:layout_width="wrap_content"
1295
+ android:text="@string/tab_1" />
1296
+ <com.google.android.material.tabs.TabItem
1297
+ android:layout_height="match_parent"
1298
+ android:layout_width="wrap_content"
1299
+ android:text="@string/tab_2" />
1300
+ <com.google.android.material.tabs.TabItem
1301
+ android:layout_height="match_parent"
1302
+ android:layout_width="wrap_content"
1303
+ android:text="@string/tab_3" />
1304
+ </com.google.android.material.tabs.TabLayout>
1305
+ ```
630
1306
 
631
- ```sh
632
- GIT_OPTIONS=q,strategy=ort # all
633
- GIT_OPTIONS_${NAME}=v,ff # project only
634
- GIT_AUTOSTASH=1 # rebase (all)
635
- GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
636
- ```
637
-
638
- | Command | Flag | ENV |
639
- | :--------- | :---------------- | :-------------------------------------------------------------------- |
640
- | branch | create | TRACK=0,1,s F|FORCE |
641
- | branch | move copy | F|FORCE |
642
- | branch | set delete | COUNT=n |
643
- | branch | global | SYNC |
644
- | checkout | branch | DETACH TRACK=s COUNT=n |
645
- | checkout | detach | REFLOG=1 |
646
- | checkout | track | COUNT=n |
647
- | checkout | global path | HEAD=s PATHSPEC=s |
648
- | checkout | * | F|FORCE MERGE |
649
- | clone | * | DEPTH=n ORIGIN=s BRANCH=s REVISION=s LOCAL=0,1 |
650
- | commit | * | UPSTREAM=s DRY_RUN EDIT=0 M|MESSAGE=s |
651
- | diff | -between -contain | MERGE_BASE |
652
- | diff | head branch | INDEX=n |
653
- | diff | * | PATHSPEC=s |
654
- | fetch | -remote | ALL |
655
- | fetch | remote | REFSPEC=s |
656
- | fetch | * | F|FORCE RECURSE_SUBMODULES=0,1,s |
657
- | git | rm | PATHSPEC=s |
658
- | log | * | PATHSPEC=s |
659
- | pull | remote | REFSPEC=s |
660
- | pull | -remote | REBASE=0,1 ALL |
661
- | pull | all | FF_ONLY=0 |
662
- | pull | * | AUTOSTASH F|FORCE RECURSE_SUBMODULES=0,1,s |
663
- | rebase | branch | HEAD=s |
664
- | rebase | onto | INTERACTIVE I HEAD=s |
665
- | reset | mode (mixed) | N REFRESH=0 |
666
- | reset | index | PATHSPEC=s |
667
- | reset | commit | COUNT=n REFLOG=1 |
668
- | reset | -commit | HEAD=s |
669
- | restore | * | PATHSPEC=s |
670
- | revbuild | global | UNTRACKED_FILES=s IGNORE_SUBMODULES=s IGNORED=s (status) |
671
- | stash | push | PATHSPEC=s |
672
- | stash | global | ALL=0,1 KEEP_INDEX=0,1 INCLUDE_UNTRACKED=0,1 STAGED=0,1 M|MESSAGE=s |
673
- | status | global | BRANCH LONG IGNORE_SUBMODULES=s,0-3 PATHSPEC=s |
674
- | submodule | -branch -url | R|RECURSIVE |
675
- | switch | detach | REFLOG=1 |
676
- | switch | -detach | HEAD=s |
677
- | switch | * | F|FORCE |
678
- | tag | add | SIGN FORCE HEAD=s M|MESSAGE=s |
679
- | tag | sign | F|FORCE HEAD=s M|MESSAGE=s |
680
- | tag | delete | COUNT=n |
681
- | rev | commit branch | HEAD=s |
682
-
683
- ### Docker
684
-
685
- * Version: 28.3
1307
+ ### Downloadable Fonts
686
1308
 
687
- ```sh
688
- DOCKER_OPTIONS=q,no-cache # all
689
- DOCKER_OPTIONS_${NAME}=v,no-cache=false # project only (override)
690
- DOCKER_TAG=latest # all
691
- DOCKER_TAG_${NAME}=v0.1.0 # project only (override)
692
- DOCKER_ALL=1 # list every image/container
693
- DOCKER_Y=1 # confirm all
694
- ```
695
-
696
- | Command | Flag | ENV |
697
- | :--------- | :---------------- | :---------------------------------------------- |
698
- | buildx | build | TAG=s |
699
- | buildx | bake | SERVICE=s |
700
- | compose | build | TARGET=s |
701
- | container | commit | REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1 |
702
- | image | rm | Y=0,1 |
703
- | image | push | TAG=s REGISTRY=s |
1309
+ Google Fonts are pre-installed and can be used without any additional configuration.
704
1310
 
705
- ### Repo
1311
+ * [Guide](https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts)
706
1312
 
707
- These global options also can target the application main suffix `${NAME}`. (e.g. REPO_ROOT_SQUARED)
1313
+ ```xml
1314
+ <!-- build.gradle -->
1315
+ dependencies {
1316
+ implementation 'androidx.appcompat:appcompat:1.6.0' <!-- createBuildDependencies = true -->
1317
+ <!-- OR -->
1318
+ implementation 'com.android.support:appcompat-v7:28.0.0'
1319
+ }
708
1320
 
709
- ```sh
710
- REPO_ROOT # parent dir
711
- REPO_HOME # project dir (main)
712
- REPO_BUILD # run,script
713
- REPO_GROUP # string
714
- REPO_REF # e.g. ruby,node
715
- REPO_DEV # pattern,0,1
716
- REPO_PROD # pattern,0,1
717
- REPO_WARN # 0,1
718
- REPO_SYNC # 0,1
719
- REPO_URL # manifest repository
720
- REPO_MANIFEST # e.g. latest,nightly,prod
721
- REPO_GROUPS # e.g. base,prod,docs
722
- REPO_STAGE # 0,1,2,3
723
- REPO_SUBMODULLES # 0,1
724
- REPO_TIMEOUT # confirm dialog (seconds)
1321
+ <!-- AndroidManifest.xml -->
1322
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- createManifest = true -->
1323
+ <application android:theme="@style/AppTheme">
1324
+ <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" />
1325
+ </application>
1326
+ </manifest>
1327
+ ```
1328
+
1329
+ ```javascript
1330
+ // https://developers.google.com/fonts/docs/developer_api
1331
+
1332
+ await android.addFontProvider(
1333
+ "com.google.android.gms.fonts",
1334
+ "com.google.android.gms",
1335
+ ["MIIEqDCCA5CgAwIBAgIJANWFuGx9007...", "MIIEQzCCAyugAwIBAgIJAMLgh0Zk..."],
1336
+ "https://www.googleapis.com/webfonts/v1/webfonts?key=1234567890" // JSON object is synchronous
1337
+ );
1338
+ /* OR */
1339
+ squared.attr("android.resource.fonts", "installGoogleFonts", false); // Use browser and local fonts only
1340
+ ```
1341
+
1342
+ ### Excluding Procedures / Applied Attributes
1343
+
1344
+ Most attributes can be excluded from the generated XML using the dataset feature in HTML. One or more can be applied to any tag using the OR "**|**" operator. These may cause warnings when you compile your project and should only be used when an extension has their custom attributes overwritten.
1345
+
1346
+ NOTE: Defining an element "**id**" will prevent it from being removed during the optimization phase.
1347
+
1348
+ ```html
1349
+ <div data-exclude-section="DOM_TRAVERSE | EXTENSION | RENDER | ALL"
1350
+ data-exclude-procedure="CONSTRAINT | LAYOUT | ALIGNMENT | ACCESSIBILITY | LOCALIZATION | CUSTOMIZATION | OPTIMIZATION | ALL"
1351
+ data-exclude-resource="BOX_STYLE | BOX_SPACING | FONT_STYLE | VALUE_STRING | IMAGE_SOURCE | ASSET | ALL"
1352
+ data-exclude-optimization="EXCLUDE | INHERIT | ALIGNMENT | POSITION | DIMENSION | MARGIN | PADDING | BASELINE | WHITESPACE | TRANSLATE | TRANSFORM | SCALING">
1353
+ </div>
1354
+ <div>
1355
+ <span data-exclude-resource="FONT_STYLE">content</span>
1356
+ <input id="cb1" type="checkbox" data-exclude-procedure="ACCESSIBILITY"><label for="cb1">checkbox text</label>
1357
+ </div>
725
1358
  ```
726
1359
 
727
1360
  ## LICENSE