squared 0.4.36 → 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,741 +1,1361 @@
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
+
26
+ ```sh
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
37
+ ```
38
+
19
39
  ```sh
20
- gem install squared
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
47
+
48
+ > npx serve
21
49
  ```
22
50
 
23
- ## Example - Rakefile
51
+ ### GitHub
24
52
 
25
- Projects from any accessible folder can be added relative to the parent directory or absolutely. Missing projects will simply be excluded from the task runner.
53
+ ```sh
54
+ > git clone https://github.com/anpham6/squared
55
+ > cd squared
26
56
 
27
- ```ruby
28
- require "squared"
57
+ > npm i
58
+ > npm run prod
29
59
 
30
- require "squared/workspace"
31
- require "squared/workspace/repo" # Optional
32
- require "squared/workspace/project/node" #
33
- require "squared/workspace/project/python" #
34
- require "squared/workspace/project/ruby" #
35
- require "squared/workspace/project/docker" #
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}
72
+ ```
73
+
74
+ ### Repo
75
+
76
+ * Python 3.6+
77
+ * [Commands](https://source.android.com/docs/setup/reference/repo)
78
+
79
+ #### Install
80
+
81
+ ```sh
82
+ mkdir -p ~/bin/repo
83
+ PATH="${HOME}/bin:${PATH}"
84
+
85
+ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
86
+ chmod a+rx ~/bin/repo
36
87
  # OR
37
- require "squared/app" # All workspace related modules
38
-
39
- # NODE_ENV = production
40
-
41
- # pathname = /workspaces/pathname
42
- # optparse = /workspaces/optparse
43
- # log = /workspaces/logger
44
- # emc = /workspaces/e-mc
45
- # pir = /workspaces/pi-r
46
- # pir2 = /workspaces/pi-r2
47
- # squared = /workspaces/squared
48
- # cli = /workspaces/squared/publish/sqd-cli
49
- # sqd-serve = /workspaces/squared/publish/sqd-serve
50
- # sqd = /workspaces/squared/sqd
51
-
52
- Workspace::Application
53
- .new(Dir.pwd, main: "squared") # Dir.pwd? (main? is implicitly basename)
54
- .banner("group", "project", styles: ["yellow", "black"], border: "bold") # name | project | path | ref | group? | parent? | version?
55
- .run("rake install", ref: :ruby)
56
- .depend(false, group: "default")
57
- .clean("rake clean", group: "default")
58
- .clean(["build/"], group: "app")
59
- .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
60
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
61
- "CFLAGS" => "-fPIC -O1"
62
- })
63
- .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", group: "default") # Uses bundler/gem_tasks (without C extensions)
64
- .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"
65
- .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
66
- .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
67
- .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
68
- .add("squared", init: 'pnpm', script: ["build:stage1", "build:stage2"], group: "app") do # Use pnpm/yarn/berry for depend + Copy target (main)
69
- # Repo (global)
70
- as(:run, "build:dev", "dev") # npm run build:dev -> npm run dev
71
- as(:run, { "build:dev": "dev", "build:prod": "prod" })
72
-
73
- add("publish/sqd-cli", "cli", exclude: [:git]) # rake cli:build
74
- add("publish/sqd-serve") # rake sqd-serve:build
75
- add("publish/sqd-admin", group: "sqd", exclude: [:base])
76
- # OR
77
- with(exclude: [:base]) { add("publish/*", "packages") } # rake packages:sqd-serve:build
78
- # OR
79
- add(["publish/sqd-cli", "publish/sqd-serve"], true, exclude: [:base]) # rake squared:sqd-serve:build
80
-
81
- # Git
82
- revbuild(include: %w[src/ framework/ types/]) # Synchronous is recommended
83
- end
84
- .add("squared/sqd", exclude: :git, pass: [:node, "checkout", "bump"]) do # Skip initialize(:node) + squared:checkout:* + squared:bump:*
85
- apply :script, "build:sqd" # Override detection
86
- apply :depend, false #
87
- apply :clean, ["build/sqd/"] # variable_set (alias)
88
- end
89
- .with(:docker, only: ["build", "compose"]) do
90
- .add("squared", "docker", file: "Dockerfile", context: ".", tag: "latest", registry: "localhost:5000", username: "squared",
91
- args: "--ssh=default",
92
- secrets: ["id=github,env=GITHUB_TOKEN"],
93
- mounts: ["src=.,dst=/project,ro,bind-propagation=rshared"]) do # Docker
94
- series(:clean) do # run | depend | doc | lint | test | copy | clean
95
- File.read(basepath("docker-bake.hcl"))
96
- .scan(/\btags\s+=\s+\["([^"]+)"\]/)
97
- .each { |val| image(:rm, tag: val.first) }
98
- end
99
- end
100
- end
101
- .pass("pull", group: "default") { test? || doc? } # rake pathname:pull | rake optparse:pull
102
- .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
103
- .build(default: "build", parallel: ["pull", "fetch", "rebase", "archive", "clean", /^outdated:/], pass: ["publish"]) do |workspace|
104
- workspace
105
- .enable_aixterm
106
- .style({
107
- banner: ["bright_cyan", "bold", "bright_black!"],
108
- border: "bright_white"
109
- })
110
- end
111
-
112
- # default = /workspaces/ruby/*
113
- # pathname = /workspaces/ruby/pathname
114
- # optparse = /workspaces/ruby/optparse
115
- # logger = /workspaces/ruby/logger
116
- # android = /workspaces/android-docs
117
- # chrome = /workspaces/chrome-docs
118
-
119
- Workspace::Application
120
- .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
121
- .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
122
- pathname: {
123
- run: "rake compile" # rake rb:pathname:build
124
- }
125
- })
126
- .with(:python, editable: false) do # ref=Symbol | group=String
127
- banner([:name, ": ", :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
128
- doc("make html") # rake rb:doc:python
129
- run(false) # rake rb:build:python (disabled)
130
- exclude(%i[base git]) # Project::Git.ref (superclass)
131
- add("android-docs", "android") # rake rb:android:doc
132
- add("chrome-docs", "chrome") # rake rb:chrome:doc
133
- end
134
- .style("inline", "bold")
135
- .build
136
- ```
137
-
138
- **NOTE**: The use of "**ref**" (class name) is only necessary when initializing an empty directory (e.g. *rake repo:init*).
139
-
140
- ### Archive
141
-
142
- ```ruby
143
- # HEADERS={"Authorization":"Bearer RANDOM-TOKEN"} | hash/json
144
- # ZIP_DEPTH=0 | default=1
145
- # TAR_DEPTH=0 | TAR_DEPTH_SQUARED
146
- # UNPACK_FORCE=1 | Remove target directory
147
-
148
- Workspace::Application
149
- .new(main: "squared")
150
- .with(:python) do
151
- add("android-docs", "android", archive: "https://github.com/anpham6/android-docs/archive/refs/tags/v0.3.0.zip")
152
- add("chrome-docs", "chrome", archive: {
153
- uri: "https://github.com/anpham6/chrome-docs/archive/refs/tags/v0.5.0.tar.gz", # URI.open (required)
154
- digest: "e3d55d2004d4770dd663254c9272dc3baad0d57a5bd14ca767de6546cdf14680", # SHA1 | SHA256 | SHA384 | SHA512 | MD5
155
- digest: "rmd160:47b7790a511eed675fec1a3e742845fef058799b", # RMD160
156
- ext: "tar.gz", # zip | tar | tar.gz | tgz | tar.xz | txz | 7z
157
- depth: 1, # nested directories (e.g. --strip-components)
158
- headers: { # URI.open
159
- "Authorization" => "Bearer RANDOM-TOKEN"
160
- }
161
- })
162
- end
163
- .add("squared", release: "https://github.com/anpham6/squared/archive/refs/tags/??") # squared:unpack:zip[v5.4.0,/tmp/squared]
164
- end
165
- ```
166
-
167
- ### Clone
168
-
169
- The task is only active when the project directory is empty or does not exist.
170
-
171
- ```ruby
172
- Workspace::Application
173
- .new(main: "squared")
174
- .git(
175
- "emc": "https://github.com/anpham6/e-mc", # rake emc:clone
176
- "pir": { # rake pir:clone
177
- uri: "https://github.com/anpham6/pi-r", #
178
- options: { #
179
- "origin": "github", # --origin=github
180
- "recurse-submodules": false, # --no-recurse-submodules
181
- "shallow-exclude": ["v0.0.1", "v0.0.2"] # --shallow-exclude=v0.0.1 --shallow-exclude=v0.0.2
182
- }
183
- }
184
- )
185
- .git("squared", "/path/to/squared", options: { local: true }) # Relative paths resolve from workspace root
186
- .git(
187
- {
188
- emc: { uri: "e-mc", options: { "depth": 2 } }, # https://github.com/anpham6/e-mc
189
- pir: "pi-r" # Maps task alias to repository folder
190
- },
191
- base: "https://github.com/anpham6", # Required
192
- repo: ["squared", "android-docs", "chrome-docs"], # https://github.com/anpham6/squared
193
- options: { # Only "repo"
194
- "depth": 1,
195
- "quiet": true
196
- }
197
- )
198
- .with(:node) do # rake clone:node
199
- add("e-mc", "emc") # https://github.com/anpham6/e-mc
200
- add("pi-r", "pir") # https://github.com/anpham6/pi-r
201
- add("squared") # https://github.com/anpham6/squared
202
- end
203
- .with(:python) do # rake clone:python
204
- add("android-docs")
205
- add("chrome-docs") do
206
- revbuild(include: "source/", exclude: ["source/conf.py"]) # Limit files being watched
207
- end
208
- end
209
- .git("https://github.com/anpham6", ["emc", "pir"]) # Targets any defined project
210
- .git("https://github.com/anpham6", cache: true) # Uses already defined root projects + revbuild
211
- .revbuild # Enables task revbuild (squared.revb)
212
- .revbuild(file: "../build.json") # $ROOT/build.json
213
- .build(parallel: ["clone"]) # rake clone + rake clone:sync
214
- ```
215
-
216
- ### Build
217
-
218
- #### Chain
219
-
220
- There has to be at least one project which uses the ``step`` attribute. Other placement attributes are ignored.
221
-
222
- **NOTE**: Projects can only reference non-global namespaced tasks. (e.g. with ":")
223
-
224
- ```ruby
225
- Workspace::Application
226
- .new
227
- .with(:node) do
228
- add("e-mc", "emc") do
229
- chain "all", "clean", step: 1 # Required
230
- chain "all", "build", step: 2
231
- end
232
- add("pi-r", "pir") do
233
- chain "all", "build", after: "emc:build" # step: 3
234
- end
235
- add("pi-r2", "pir2") do
236
- chain "all", "build", before: "squared" # step: 3
237
- end
238
- add("squared-express", "express") do
239
- chain "all", "clean", with: "emc" # step: 1
240
- chain "all", "build", with: "pir" # step: 3
241
- end
242
- add("squared") do
243
- revbuild(include: %w[src/ framework/ types/]) # Git revision build command (optional)
244
- chain "all", "revbuild", after: "express:build" # step: 4
245
- chain "publish", "bump:patch", "publish:latest", step: 0, sync: true # rake publish -> squared:bump:patch -> squared:publish:latest
246
- end
247
- end
248
- .with(:python) do
249
- doc("make html")
250
- add("android-docs") do
251
- chain "all", "doc", with: "squared", after: "squared" # step: 4
252
- end
253
- add("chrome-docs") do
254
- chain "all", "doc", with: "squared", before: "squared:revbuild" # Same
255
- end
256
- end
257
- .chain "all", "status", with: "squared", after: "android-docs" # Global tasks (e.g. without ":")
258
- .build
88
+ scripts/repo-install.sh ~/bin
259
89
  ```
260
90
 
91
+ #### Usage
92
+
261
93
  ```sh
262
- rake all # all[1-3-4]
263
- rake all:print
264
- ```
265
-
266
- Threaded is the default when there are two or more tasks. Using ``with`` and either **before** or **after** will create a synchronous group.
267
-
268
- * Step 1: emc:clean + express:clean (thread)
269
- * Step 2: emc:build (sync)
270
- * Step 3: pir:build + express:build + pir2:build (thread)
271
- * Step 4: chrome-docs:doc + squared:revbuild + android-docs:doc + status (sync)
272
-
273
- #### Graph
274
-
275
- ```ruby
276
- Workspace::Application
277
- .new(main: "squared")
278
- .graph(["depend"], ref: :git) # Optional
279
- .with(:python) do
280
- doc(windows? ? ".\make.bat html" : "make html") # rake android-docs:doc | rake doc:python
281
- add("android-docs", "android", venv: "/home/user/.venv") # rake android-docs:depend
282
- add("chrome-docs", "chrome", graph: "android", venv: %w[.venv --clear]) do # /workspaces/chrome-docs/.venv
283
- apply :dependindex, 1 # Use Poetry for dependencies (optional)
284
- end
285
- end
286
- .with(:node) do
287
- graph(["build", "copy"], on: { # Overrides "git"
288
- first: proc { puts "1" },
289
- last: proc { puts "2" }
290
- })
291
- script("build:dev") # npm run build:dev
292
- # OR
293
- run([nil, "build:dev", { "PATH" => "~/.bin" }, "--workspace", "--silent"]) # PATH="~/.bin" npm run build:dev --workspace -- --silent
294
- # OR
295
- run({ # Same
296
- script: "build:dev", #
297
- env: { "PATH" => "~/.bin" }, #
298
- opts: "--workspace", #
299
- args: "--silent" #
300
- })
301
-
302
- add("e-mc", "emc") do
303
- first("build", "emc:clean", "emc:depend") # rake emc:clean && rake emc:depend && rake emc:build && echo "123"
304
- last("build", out: "123") { |out: nil| puts out } #
305
- error("build") { |err: nil| log.debug err } #
306
- end
307
- add("pi-r", "pir", graph: "emc", first: {
308
- build: proc { puts self.name } # puts "pir"
309
- })
310
- add("squared-express", "express", graph: "pir")
311
- add("squared", graph: ["chrome", "express"]) do
312
- first("git:ls-files") { puts "1" } # skipped
313
- first("git:ls-files", override: true) { puts "2" } # puts "2"
314
- last("git:ls-files") { puts workspace.root } # puts "/workspaces"
315
- end
316
- end
317
- .with(:ruby) do
318
- run("gem build") # gem build
319
- # OR
320
- run("gem build", on: { first: -> { p "2" }, last: -> { p "4" } }) do # run | depend | graph | clean | doc | lint | test
321
- p "1"
322
- end
323
- # OR
324
- run(on: { first: -> { p "pass" }, last: -> { p "pass" } }) do
325
- p "1"
326
- end
327
- # OR
328
- run(["gem build", "--force", { "RUBY_VERSION" => "3.4.0" }]) # RUBY_VERSION="3.4.0" gem build --force
329
- # OR
330
- run({ #
331
- command: "gem build", # RUBY_VERSION="3.4.0" gem build --silent --force
332
- opts: "--force", # composable
333
- env: { "PATH" => "~/.bin" }, #
334
- args: { silent: true } #
335
- })
336
- # OR
337
- run(["gem pristine", ["gem build", "gem cleanup"], nil, "--debug"]) # gem pristine --debug && gem build --debug && gem cleanup --debug
338
- #
339
- # All commands are either Array or Hash
340
- #
341
- run([ # PATH="~/.bin" GEM_HOME="~/.gems/ruby-3.4.0" (merged)
342
- ["gem pristine", "--all", { "PATH" => "~/.bin" }, "--silent"], # gem pristine --silent --all
343
- ["gem build", { strict: true }, { "GEM_HOME" => "~/.gems/ruby-3.4.0" }] # gem build --strict
344
- ]) #
345
- # OR
346
- run([ # Same
347
- { #
348
- env: { "PATH" => "~/.bin" }, #
349
- command: "gem pristine", #
350
- opts: "--all", args: "--silent" #
351
- }, #
352
- { #
353
- env: { "GEM_HOME" => "~/.gems/ruby-3.4.0" }, #
354
- command: "gem build", #
355
- opts: { strict: true } #
356
- } #
357
- ])
358
-
359
- add("pathname", test: ["rake test", { jobs: ENV["RAKE_JOBS"] }]) # rake test --jobs 4
360
- add("fileutils", graph: "pathname")
361
- add("optparse", run: "gem build", env: { "PATH" => "~/.bin" }, opts: "-v") # PATH="~/.bin" gem build -v
362
- add("rake", graph: ["fileutils", "optparse"])
363
- banner(command: false) # Always hide banner
364
- end
365
- .build
94
+ mkdir workspaces
95
+ cd workspaces
96
+
97
+ repo init -u https://github.com/anpham6/squared-repo -m latest.xml
98
+ repo sync -j4
99
+
100
+ cd squared
101
+ npm i
366
102
  ```
367
103
 
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
+
368
108
  ```sh
369
- rake pir:graph # emc + pir
370
- rake express:graph # emc + pir + express
371
- rake chrome:graph # android + chrome
372
- rake graph:python # same
373
- rake squared:graph # android + chrome + emc + pir + express + squared
374
- rake graph:node # same
375
- rake rake:graph # pathname + fileutils + optparse + rake
376
- rake graph:ruby # same
377
- rake graph # graph:node + graph:ruby
109
+ mkdir workspaces
110
+ cd workspaces # REPO_ROOT
111
+
112
+ wget https://unpkg.com/squared/Rakefile
378
113
 
379
- rake squared:graph:run[express,pir] # emc + pir + express + squared
380
- rake squared:graph:run[node,-emc] # pir + express + squared
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
121
+ # OR
122
+ REPO_ROOT=/tmp/123 NODE_INSTALL=pnpm repo:init
123
+ # OR - without Repo
124
+ rake clone:node # master
125
+ rake clone # node + docs
381
126
  ```
382
127
 
383
- ### Tasks
128
+ > [!TIP]
129
+ > Use the supplied Rakefile inside the **squared** project folder once the source has been downloaded.
384
130
 
385
- ```ruby
386
- Workspace::Series.batch(:ruby, :node, {
387
- stage: %i[graph test],
388
- reset: %i[stash pull]
389
- })
131
+ #### Docker
390
132
 
391
- Workspace::Series.rename("depend", "install")
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
392
163
  ```
393
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
+
394
178
  ## Usage
395
179
 
396
- ```sh
397
- rake -T # List tasks
398
- rake # rake status (usually "build")
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
399
352
 
400
- # GIT_OPTIONS=rebase
401
- rake pull # All except "default" + "app"
402
- rake pull:ruby # pathname + optparse + logger
403
- rake pull:default # pathname + optparse
404
- rake pull:app # squared
405
- rake pull:node # emc + pir + squared
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.
406
354
 
407
- rake build # All except "android"
408
- rake doc # optparse + android
409
- rake depend # All except "default"
355
+ * ES2020
410
356
 
411
- rake build:ruby # rake compile + rake install + rake install
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 */});
412
364
 
413
- rake clean # All except "default" + "app"
414
- rake clean:ruby # rake clean + rake clean + ["tmp/"]
415
- rake clean:default # rake clean + rake clean + skip
416
- rake clean:app # none + skip + ["build/"]
417
- rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
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 */});
418
372
 
419
- rake squared:run[#] # List scripts (node)
420
- rake squared:rake[#] # List tasks (ruby)
373
+ // Observe
374
+ await squared.copyTo(/* directory */, { useOriginalHtmlPage: false, observe: /* Same as Android */ | true /* Auto-reload */}).then(() => squared.observe());
375
+ });
376
+ </script>
421
377
  ```
422
378
 
423
- ```sh
424
- rake build:app # squared + cli + sqd-serve
425
- rake squared:build:workspace # cli + sqd-serve
426
- rake pull:sqd # sqd-admin
427
- rake squared:pull:workspace # sqd-serve + sqd-admin
428
- rake squared:outdated:workspace # cli + sqd-serve + sqd-admin
429
- ```
430
-
431
- ## Methods
432
-
433
- Task:
434
-
435
- * run
436
- * script
437
- * depend
438
- * archive
439
- * graph
440
- * doc
441
- * lint
442
- * test
443
- * clean
444
-
445
- Non-task:
446
-
447
- * log
448
- * exclude
449
-
450
- ## Styles
451
-
452
- * banner
453
- * border
454
- * header
455
- * active
456
- * inline
457
- * subject
458
- * border
459
- * warn
460
- * caution
461
- * current
462
- * latest
463
- * extra
464
- * major
465
- * red
466
- * yellow
467
- * green
468
-
469
- ## Git
470
-
471
- Most project classes will inherit from `Git` which enables these tasks:
472
-
473
- | Task | Git | Command |
474
- | :--------- | :--------------- | :-------------------------------------------- |
475
- | branch | branch | create track delete move copy list current |
476
- | checkout | checkout | commit branch track detach path |
477
- | commit | commit | add all amend amend-orig fixup |
478
- | diff | diff | head branch files view between contain |
479
- | fetch | fetch | origin remote all |
480
- | files | ls-files | cached modified deleted others |
481
- | git | | add blame clean mv revert rm status |
482
- | log | log | view between contain |
483
- | merge | merge | commit no-commit send |
484
- | pull | pull | origin remote all |
485
- | rebase | rebase | branch onto send |
486
- | refs | ls-remote --refs | heads tags remote |
487
- | reset | reset | commit index patch mode undo |
488
- | restore | restore | source staged worktree |
489
- | rev | rev | commit build output |
490
- | show | show | format oneline textconv |
491
- | stash | stash | push pop apply branch drop clear list |
492
- | submodule | submodule | status update branch url sync |
493
- | switch | switch | branch create detach |
494
- | tag | tag | add sign delete list |
495
-
496
- You can disable all of them at once using the `exclude` property.
497
-
498
- ```ruby
499
- Workspace::Application.exclude('autostash', 'rebase')
500
-
501
- Workspace::Application
502
- .new
503
- .add("squared", exclude: :git)
504
- ```
505
-
506
- You can disable one or more of them using the `pass` property as a *string*.
507
-
508
- ```ruby
509
- Workspace::Application
510
- .new
511
- .add("squared", pass: ["pull"], ref: :node)
512
- .pass("pull", ref: :node) { read_packagemanager(:private) }
513
- ```
514
-
515
- ### Commit Hash
516
-
517
- Commands which use commit hashes are parsed using a ":" prefix as to not be confused for an option.
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
+ ```
518
403
 
519
- ```sh
520
- rake squared:log:view[:af012345] # git log af012345
521
- rake squared:log:view[#{af012345}] # deprecated
522
- rake squared:log:view[H1,HEAD^5,all,lib,./H12345] # git log --all @~1 @^5 -- 'lib' 'H12345'
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
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
+ };
523
576
  ```
524
577
 
525
- ## Environment
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
+ };
526
607
 
527
- ### Path
608
+ // Optional (Same as Android)
528
609
 
529
- All project binary programs can have their executable path set to a non-global alias.
610
+ ```
530
611
 
531
- ```ruby
532
- Common::PATH.update({
533
- GIT: "/usr/bin/git", # PATH_GIT=/usr/bin/git
534
- TAR: "/opt/archivers/tar", # PATH_TAR=/opt/archivers/tar
535
- UNZIP: "/opt/archivers/unzip",
536
- GEM: "~/.rvm/gems/ruby-3.4.0/bin/gem",
537
- BUNDLE: "~/.rvm/gems/ruby-3.4.0/bin/bundle",
538
- RAKE: "~/.rvm/gems/ruby-3.4.0/bin/rake",
539
- NPM: "/opt/node/v22.0.0/bin/npm",
540
- PYTHON: "#{ENV["PYTHONPATH"]}/bin/python"
541
- })
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
+ };
542
624
  ```
543
625
 
544
- ### Build
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.
545
629
 
546
- ```ruby
547
- Workspace::Application
548
- .new
549
- .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
630
+ ```javascript
631
+ // Save
632
+ squared.setFramework(android, { compressImages: true }, "android-example");
633
+
634
+ // Load
635
+ squared.setFramework(android, "android-example");
550
636
  ```
551
637
 
552
- ```sh
553
- BUILD_TYPE # global
554
- ${PROG}_COLOR=0 # --no-color (e.g. GIT_COLOR)
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)
555
670
 
556
- # :env :run :args :opts :type
557
- # LD_LIBRARY_PATH="path/to/lib" CFLAGS="-Wall" gcc a.c -o a.o -g -O2
558
- BUILD_${NAME} # gcc a.c -o a.o
559
- BUILD_${NAME}_OPTS # -g
560
- BUILD_${NAME}_ENV # {"LD_LIBRARY_PATH":"path/to/lib","CFLAGS":"-Wall"} (hash/json)
561
- BUILD_${NAME}_TYPE # debug
671
+ latest(count?: number) // Most recent parseDocument session ids (1 newest / -1 oldest: string, other: string[])
562
672
 
563
- # :env :script :opts :args
564
- # NODE_ENV="production" NO_COLOR="1" npm run build:dev --loglevel=error --workspaces=false -- --quiet
565
- BUILD_${NAME} # build:dev
566
- BUILD_${NAME}_OPTS # --loglevel=error --workspaces=false
567
- BUILD_${NAME}_ENV # {"NODE_ENV":"production","NO_COLOR":"1"} (hash/json)
568
- BUILD_${NAME}_DEV # pattern,0,1 (:dev)
569
- BUILD_${NAME}_PROD # pattern,0,1 (:prod)
570
- ${REF}_${NAME}_OPTS # --quiet (e.g. NODE_SQUARED_OPTS)
673
+ auth(token: string) // Set JWT authorization token for all requests
571
674
 
572
- BUILD_${NAME}=0 # skip project
573
- BUILD_${NAME}_VERSION=0.1.0 # publish + detection
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)
574
678
 
575
- BANNER=0 # hide banner
576
- BANNER_${NAME}=0 #
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
577
682
 
578
- VERBOSE=0 # console output level
579
- VERBOSE_${NAME}=0 # 0,1,2,n
683
+ toString() // Current framework loaded
684
+ toString(projectId: string) // await squared.close(projectId) (required)
580
685
 
581
- REVBUILD_FORCE=1 # Rebuild all targets
582
- REVBUILD_FORCE_${NAME}=1 # Rebuild project
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
583
691
 
584
- PREREQS_${NAME}=build,copy # Class method name to invoke
585
- PREREQS_${REF}=depend # e.g. Node
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)
586
709
  ```
587
710
 
588
- ### Graph
711
+ Packaging methods will return a Promise and requires a squared-express installation. These features are not supported when the framework is VDOM.
589
712
 
590
- ```sh
591
- GRAPH_${NAME} # depend,build => squared:depend + squared:build
592
- GRAPH_${NAME}_PASS # -emc,pir,express => pir + express
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)
593
734
  ```
594
735
 
595
- ### Logger
736
+ ## Extending Node object
596
737
 
597
- These global options also can target the project suffix `${NAME}`. (e.g. LOG_FILE_EMC)
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.
598
739
 
599
- ```sh
600
- LOG_FILE # %Y-%m-%d.log
601
- # OR
602
- LOG_AUTO # year,y,month,m,day,d,1
603
- # Optional
604
- LOG_DIR # exist?
605
- LOG_LEVEL # See gem "logger"
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);
758
+ }
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
+ });
606
767
  ```
607
768
 
608
- ### Git
769
+ ## Forwarding Request
609
770
 
610
- * Version: 2.50
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.
611
772
 
612
- ```sh
613
- GIT_OPTIONS=q,strategy=ort # all
614
- GIT_OPTIONS_${NAME}=v,ff # project only
615
- GIT_AUTOSTASH=1 # rebase (all)
616
- GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
617
- ```
618
-
619
- | Command | Flag | ENV |
620
- | :--------- | :---------------- | :-------------------------------------------------------------------- |
621
- | branch | create | TRACK=0,1,s F|FORCE |
622
- | branch | move copy | F|FORCE |
623
- | branch | delete | COUNT=n |
624
- | branch | global | SYNC |
625
- | checkout | branch | DETACH TRACK=s COUNT=n |
626
- | checkout | detach | REFLOG=1 |
627
- | checkout | track | COUNT=n |
628
- | checkout | global path | HEAD=s PATHSPEC=s |
629
- | checkout | * | F|FORCE MERGE |
630
- | clone | * | DEPTH=n ORIGIN=s BRANCH=s REVISION=s LOCAL=0,1 |
631
- | commit | * | UPSTREAM=s DRY_RUN EDIT=0 M|MESSAGE=s |
632
- | diff | -between -contain | MERGE_BASE |
633
- | diff | head branch | INDEX=n |
634
- | diff | * | PATHSPEC=s |
635
- | fetch | -remote | ALL |
636
- | fetch | remote | REFSPEC=s |
637
- | fetch | * | F|FORCE RECURSE_SUBMODULES=0,1,s |
638
- | git | rm | PATHSPEC=s |
639
- | log | * | PATHSPEC=s |
640
- | pull | remote | REFSPEC=s |
641
- | pull | -remote | REBASE=0,1 ALL |
642
- | pull | all | FF_ONLY=0 |
643
- | pull | * | AUTOSTASH F|FORCE RECURSE_SUBMODULES=0,1,s |
644
- | rebase | branch | HEAD=s |
645
- | rebase | onto | INTERACTIVE I HEAD=s |
646
- | reset | mode (mixed) | N REFRESH=0 |
647
- | reset | index | PATHSPEC=s |
648
- | reset | commit | COUNT=n REFLOG=1 |
649
- | reset | -commit | HEAD=s |
650
- | restore | * | PATHSPEC=s |
651
- | revbuild | global | UNTRACKED_FILES=s IGNORE_SUBMODULES=s IGNORED=s (status) |
652
- | stash | push | PATHSPEC=s |
653
- | stash | global | ALL=0,1 KEEP_INDEX=0,1 INCLUDE_UNTRACKED=0,1 STAGED=0,1 M|MESSAGE=s |
654
- | status | global | BRANCH LONG IGNORE_SUBMODULES=s,0-3 PATHSPEC=s |
655
- | submodule | -branch -url | R|RECURSIVE |
656
- | switch | detach | REFLOG=1 |
657
- | switch | -detach | HEAD=s |
658
- | switch | * | F|FORCE |
659
- | tag | add | SIGN FORCE HEAD=s M|MESSAGE=s |
660
- | tag | sign | F|FORCE HEAD=s M|MESSAGE=s |
661
- | tag | delete | COUNT=n |
662
- | rev | commit branch | HEAD=s |
663
-
664
- ### Docker
665
-
666
- * Version: [28.5](https://docs.docker.com/engine/release-notes/28)
773
+ ```javascript
774
+ squared.setHostname("http://hostname:8000");
775
+ // OR
776
+ squared.setHostname(); // Reset to window.location (e.g. localhost:3000)
667
777
 
668
- ```sh
669
- DOCKER_OPTIONS=q,no-cache # all
670
- DOCKER_OPTIONS_${NAME}=v,no-cache=false # project only (override)
671
- DOCKER_TAG=latest # all
672
- DOCKER_TAG_${NAME}=v0.1.0 # project only (override)
673
- DOCKER_ALL=1 # list every image/container
674
- DOCKER_Y=1 # confirm all
675
- ```
676
-
677
- | Command | Flag | ENV |
678
- | :--------- | :---------------- | :---------------------------------------------- |
679
- | buildx | build | TAG=s |
680
- | buildx | bake | SERVICE=s |
681
- | compose | build | TARGET=s |
682
- | container | commit | REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1 |
683
- | image | rm | Y=0,1 |
684
- | image | push | TAG=s REGISTRY=s |
778
+ await squared.saveAs("chrome.zip"); // Current browser
779
+ // OR
780
+ await squared.copyTo("/path/project"); // Remote server
781
+ ```
685
782
 
686
- ### Repo
783
+ ## Broadcasting
687
784
 
688
- * [Repo](https://source.android.com/docs/setup/reference/repo)
689
- * https://github.com/anpham6/squared-repo
690
- * Python 3.6
691
- * Not compatible with Windows
785
+ Console messages (stdout) can be sent to the browser console instead through DevTools.
692
786
 
693
- ```sh
694
- mkdir -p ~/.bin
695
- PATH="${HOME}/.bin:${PATH}"
696
- curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
697
- chmod a+rx ~/.bin/repo
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
+ });
698
797
  ```
699
798
 
700
- ```ruby
701
- # REPO_ROOT = /workspaces |
702
- # REPO_HOME = /workspaces/squared | Dir.pwd
703
- # rake = /workspaces/squared/Rakefile | main?
704
- #
705
- # OR
706
- #
707
- # REPO_ROOT = /workspaces | Dir.pwd
708
- # rake = /workspaces/Rakefile |
709
- # REPO_HOME = /workspaces/squared | main: "squared"
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
+ }
710
813
 
711
- Workspace::Application
712
- .new(main: "squared")
713
- .repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "prod"], ref: :node)
714
- .add("squared", script: ["build:stage1", "build:stage2"])
715
- .build
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)
716
838
  ```
717
839
 
718
- These global options also can target the application main suffix `${NAME}`. (e.g. *REPO_ROOT_SQUARED*)
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.
719
841
 
720
- ```sh
721
- REPO_ROOT # parent dir
722
- REPO_HOME # project dir (main)
723
- REPO_BUILD # run,script
724
- REPO_GROUP # string
725
- REPO_REF # e.g. ruby,node
726
- REPO_DEV # pattern,0,1
727
- REPO_PROD # pattern,0,1
728
- REPO_WARN # 0,1
729
- REPO_SYNC # 0,1
730
- REPO_GIT # manifest repository
731
- REPO_MANIFEST # e.g. latest,nightly,prod
732
- REPO_GROUPS # e.g. base,prod,docs
733
- REPO_STAGE # 0,1,2,3,4
734
- REPO_SUBMODULLES # 0,1
735
- REPO_TIMEOUT # confirm dialog (seconds)
736
- ```
737
-
738
- Other similarly cloned `Repo` repositories can be managed remotely by setting the `REPO_ROOT` environment variable to the location.
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"
871
+ },
872
+ "_": { // Non-namespaced attributes
873
+ style: "@style/Widget.Material3.Button.TextButton"
874
+ }
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
887
+ ```
888
+
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
+ });
898
+ ```
899
+
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.
903
+
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
+ });
914
+ ```
915
+
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
+ });
982
+
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.
987
+
988
+ ```
989
+ data-viewmodel-{namespace}-{attribute} -> data-viewmodel-android-text
990
+ ```
991
+
992
+ These two additional output parameters are required when using the "**data-viewmodel**" prefix.
993
+
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
+ ```
1002
+
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
+ ```
1030
+
1031
+ ### Layout Includes / Merge Tag
1032
+
1033
+ Some applications can benefit from using includes or merge tags to share common templates. Nested includes is supported.
1034
+
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
+ ```
1044
+
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 -->
1094
+ ```
1095
+
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.
1097
+
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>
1128
+ ```
1129
+
1130
+ Using `target` into a ConstraintLayout or RelativeLayout container will not include automatic positioning.
1131
+
1132
+ ### Custom Attributes
1133
+
1134
+ System or extension generated attributes can be overridden preceding finalization. They will only be visible on the declared framework.
1135
+
1136
+ ```
1137
+ data-android-attr-{namespace}? -> Default is "android"
1138
+ ```
1139
+
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
+ ```
1146
+
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" />
1153
+ ```
1154
+
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
1163
+
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*.
1165
+
1166
+ ```java
1167
+ import android.graphics.drawable.Animatable;
1168
+
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
+ ```
1175
+
1176
+ ### Jetpack Compose
1177
+
1178
+ Most mobile applications do not have a deeply nested hierarchy and are generally better to implement using declarative programming.
1179
+
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
1191
+ ```
1192
+
1193
+ You can also do it using the "**android.substitute**" extension directly inside the HTML element.
1194
+
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
+ });
1223
+ ```
1224
+
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
+ ```
1237
+
1238
+ Compose will remove child elements by default. You can preserve them by explictly using the renderChildren property. (data-android-substitute-render-children="true")
1239
+
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
+ });
1271
+ ```
1272
+
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
+ ```
1286
+
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
+ ```
1306
+
1307
+ ### Downloadable Fonts
1308
+
1309
+ Google Fonts are pre-installed and can be used without any additional configuration.
1310
+
1311
+ * [Guide](https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts)
1312
+
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
+ }
1320
+
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>
1358
+ ```
739
1359
 
740
1360
  ## LICENSE
741
1361