mirror of
https://github.com/dudaanton/obsidian-strudel-plugin.git
synced 2026-07-22 06:43:01 +00:00
Add base plugin logic
This commit is contained in:
parent
2f74420965
commit
104bc3553d
181 changed files with 21479 additions and 12872 deletions
2
.env.example
Normal file
2
.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
TARGET_VAULT_DIR="/path/to/plugin/dir"
|
||||
TARGET_TEST_VAULT_DIR="/path/to/plugin/dir"
|
||||
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
84
.eslintrc
Normal file
84
.eslintrc
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"parser": "vue-eslint-parser",
|
||||
"plugins": ["@typescript-eslint/eslint-plugin", "eslint-plugin-vue", "prettier"],
|
||||
"extends": ["plugin:@typescript-eslint/recommended", "plugin:vue/vue3-recommended", "plugin:prettier/recommended"],
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"docker-formatter.*",
|
||||
"node_modules/*",
|
||||
".nuxt/*",
|
||||
"dist/*",
|
||||
"dist-electron/*",
|
||||
"server/*",
|
||||
"scripts/*",
|
||||
"src/assets/*"
|
||||
],
|
||||
"rules": {
|
||||
"arrow-body-style": 0,
|
||||
"class-methods-use-this": 0,
|
||||
"comma-dangle": 0,
|
||||
"func-names": 0,
|
||||
"import/extensions": 0,
|
||||
"import/no-dynamic-require": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/no-named-as-default-member": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"linebreak-style": 0,
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 120,
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreComments": true,
|
||||
"ignoreStrings": true
|
||||
}
|
||||
],
|
||||
"no-await-in-loop": 0,
|
||||
"no-console": 0,
|
||||
"no-loop-func": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-shadow": 0,
|
||||
"no-trailing-spaces": 1,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-use-before-define": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"semi": 0,
|
||||
"space-before-function-paren": 0,
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/no-dupe-keys": "warn",
|
||||
"vue/no-mutating-props": ["warn", { "shallowOnly": true }],
|
||||
"vue/no-unused-vars": ["warn", { "ignorePattern": "^_" }],
|
||||
"vue/no-use-v-if-with-v-for": "warn",
|
||||
"vue/prop-name-casing": "error",
|
||||
"vue/return-in-computed-property": "off",
|
||||
"vue/require-prop-types": "error",
|
||||
"vue/require-toggle-inside-transition": "warn",
|
||||
"vue/require-typed-object-prop": "error",
|
||||
"vue/require-v-for-key": "warn",
|
||||
"vue/valid-v-for": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-inferrable-types": ["warn", { "ignoreParameters": true }],
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"prettier/prettier": ["warn"]
|
||||
}
|
||||
}
|
||||
|
||||
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
|
||||
# Don't include the compiled main.js file in the repo.
|
||||
# They should be uploaded to GitHub releases instead.
|
||||
main.js
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
||||
# obsidian
|
||||
data.json
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
||||
build/*
|
||||
|
||||
.env
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
tag-version-prefix=""
|
||||
15
.prettierrc.mjs
Normal file
15
.prettierrc.mjs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @type {import('prettier').Options}
|
||||
*/
|
||||
const config = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
importOrder: ['^@/(.*)$', '^[./]'],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
}
|
||||
|
||||
export default config
|
||||
622
LICENSE
Normal file
622
LICENSE
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Strudel Obsidian plugin
|
||||
|
||||
TODO
|
||||
41
build_and_copy_plugin.sh
Executable file
41
build_and_copy_plugin.sh
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
source ./.env
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
# Define plugin source and build directories
|
||||
PLUGIN_DIR="."
|
||||
BUILD_DIR="$PLUGIN_DIR/build"
|
||||
|
||||
echo "--- Building Strudel Obsidian Plugin ---"
|
||||
|
||||
# Install dependencies if node_modules doesn't exist
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "Node modules not found. Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Run the build script
|
||||
echo "Running build..."
|
||||
npm run build
|
||||
|
||||
echo "--- Copying Plugin Files to Test Vault ---"
|
||||
|
||||
# Create target directory in test vault if it doesn't exist
|
||||
mkdir -p "$TARGET_TEST_VAULT_DIR"
|
||||
echo "Ensured target directory exists: $TARGET_TEST_VAULT_DIR"
|
||||
|
||||
# Copy built files
|
||||
cp "$BUILD_DIR/main.js" "$TARGET_TEST_VAULT_DIR/main.js"
|
||||
echo "Copied main.js"
|
||||
|
||||
cp "$BUILD_DIR/main.css" "$TARGET_TEST_VAULT_DIR/styles.css"
|
||||
echo "Copied styles.css"
|
||||
|
||||
cp "$PLUGIN_DIR/manifest.json" "$TARGET_TEST_VAULT_DIR/manifest.json"
|
||||
echo "Copied manifest.json"
|
||||
|
||||
echo "--- Plugin Build and Copy Complete ---"
|
||||
echo "Plugin files are ready in: $TARGET_TEST_VAULT_DIR"
|
||||
35
build_and_copy_plugin_prod.sh
Executable file
35
build_and_copy_plugin_prod.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
# Define plugin source and build directories
|
||||
PLUGIN_DIR="."
|
||||
BUILD_DIR="$PLUGIN_DIR/build"
|
||||
|
||||
echo "--- Building Strudel Obsidian Plugin ---"
|
||||
|
||||
# Install dependencies if node_modules doesn't exist
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "Node modules not found. Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Run the build script
|
||||
echo "Running build..."
|
||||
npm run build
|
||||
|
||||
echo "--- Copying Plugin Files to Test Vault ---"
|
||||
|
||||
# Copy built files
|
||||
cp -X "$BUILD_DIR/main.js" "$TARGET_VAULT_DIR/main.js"
|
||||
echo "Copied main.js"
|
||||
|
||||
cp -X "$BUILD_DIR/main.css" "$TARGET_VAULT_DIR/styles.css"
|
||||
echo "Copied styles.css"
|
||||
|
||||
cp -X "$PLUGIN_DIR/manifest.json" "$TARGET_VAULT_DIR/manifest.json"
|
||||
echo "Copied manifest.json"
|
||||
|
||||
echo "--- Plugin Build and Copy Complete ---"
|
||||
echo "Plugin files are ready in: $TARGET_VAULT_DIR"
|
||||
9
manifest.json
Normal file
9
manifest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "strudel-repl",
|
||||
"name": "Strudel REPL",
|
||||
"version": "0.0.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "",
|
||||
"author": "Anton Duda",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
6714
package-lock.json
generated
Normal file
6714
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
62
package.json
Normal file
62
package.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"name": "strudel-obsidian-plugin",
|
||||
"version": "0.0.1",
|
||||
"description": "Strudel REPL Obsidian plugin",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "vite preview",
|
||||
"build": "tsc -noEmit -skipLibCheck && vite build",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/luxon": "^3.6.2",
|
||||
"@types/node": "^24.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.61.0",
|
||||
"@typescript-eslint/parser": "5.61.0",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"esbuild-plugin-vue3": "^0.3.2",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-prettier": "5.0.0",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"obsidian": "latest",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-eslint": "16.1.2",
|
||||
"sass-embedded": "^1.93.2",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vue-tsc": "^2.2.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/language": "https://github.com/lishid/cm-language",
|
||||
"@codemirror/merge": "^6.11.0",
|
||||
"@internationalized/date": "^3.8.2",
|
||||
"@tonaljs/tonal": "^4.10.0",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"acorn": "^8.15.0",
|
||||
"chord-voicings": "^0.0.1",
|
||||
"dayjs": "^1.11.18",
|
||||
"escodegen": "^2.1.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"file-type": "^20.5.0",
|
||||
"fraction.js": "^5.3.4",
|
||||
"front-matter": "^4.0.2",
|
||||
"lucide-vue-next": "^0.284.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nanostores": "^1.0.1",
|
||||
"pinia": "^3.0.2",
|
||||
"sfumato": "^0.1.2",
|
||||
"soundfont2": "^0.5.0",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.16",
|
||||
"webmidi": "^3.1.14"
|
||||
}
|
||||
}
|
||||
9
src/App.vue
Normal file
9
src/App.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<div class="strudel">
|
||||
<Views />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Views from './components/Views.vue'
|
||||
</script>
|
||||
28
src/commands/createStrudelBlock.ts
Normal file
28
src/commands/createStrudelBlock.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { STRUDEL_CODEBLOCK_KEYWORD } from '@/constants/keywords'
|
||||
import { Editor, Notice } from 'obsidian'
|
||||
|
||||
export const createStrudelBlock = async (editor: Editor) => {
|
||||
if (!editor) {
|
||||
new Notice('No active markdown editor found.', 3000)
|
||||
return
|
||||
}
|
||||
|
||||
const cursor = editor.getCursor()
|
||||
const shouldCreateNewLine = cursor.ch > 0
|
||||
const selection = editor.getSelection()
|
||||
|
||||
const strudelBlock = `\`\`\`${STRUDEL_CODEBLOCK_KEYWORD}
|
||||
|
||||
\`\`\``
|
||||
|
||||
|
||||
if (selection) {
|
||||
editor.replaceSelection(strudelBlock)
|
||||
} else {
|
||||
// If no selection, just insert the linkPath at the cursor position
|
||||
editor.replaceRange(strudelBlock, cursor)
|
||||
}
|
||||
|
||||
// setting cursor to the line inside the new strudel block
|
||||
editor.setSelection({ line: cursor.line + (shouldCreateNewLine ? 2 : 1), ch: 0 })
|
||||
}
|
||||
120
src/components/StrudelBlockView.vue
Normal file
120
src/components/StrudelBlockView.vue
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div class="strudel-block">
|
||||
<div class="strudel-block__header">
|
||||
<ObsidianIcon
|
||||
:icon="isCurrentBlockPlaying ? 'circle-stop' : 'play'"
|
||||
@click="isCurrentBlockPlaying ? stop() : play()"
|
||||
/>
|
||||
<ObsidianIcon v-if="isCurrentBlockPlaying" icon="refresh-ccw" @click="play()" />
|
||||
</div>
|
||||
<canvas v-show="isCurrentBlockPlaying" ref="canvas"></canvas>
|
||||
<!-- <div ref="editorRef"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ObsidianIcon from './obsidian/Icon.vue'
|
||||
import { Strudel } from '@/entities/Strudel'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { initStrudel } from '@/strudel/init.js'
|
||||
import { GlobalStore } from '@/stores/GlobalStore'
|
||||
import { updateMiniLocations } from '@/editor/StrudelHighlight'
|
||||
import { MarkdownView, WorkspaceLeaf } from 'obsidian'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
const props = defineProps<{
|
||||
strudelBlock: Strudel
|
||||
}>()
|
||||
|
||||
const editorRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const { isPlaying, currentBlock } = GlobalStore.getInstance()
|
||||
|
||||
const isCurrentBlockPlaying = computed(() => {
|
||||
return isPlaying.value && currentBlock.value?.id === props.strudelBlock.id
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.strudelBlock,
|
||||
(newBlock) => {
|
||||
if (isCurrentBlockPlaying.value) {
|
||||
console.log(GlobalStore.getInstance().repl)
|
||||
// GlobalStore.getInstance().repl.setCode(newBlock.code)
|
||||
// GlobalStore.getInstance().resetMiniLocations()
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
//
|
||||
// const editor = new StrudelMirror({
|
||||
// defaultOutput: webaudioOutput,
|
||||
// getTime: () => getAudioContext().currentTime,
|
||||
// transpiler,
|
||||
// root: document.getElementById('editor'),
|
||||
// initialCode: funk42,
|
||||
// drawTime,
|
||||
// onDraw: (haps, time) => drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 0 }),
|
||||
// prebake: async () => {
|
||||
// initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
|
||||
// const loadModules = evalScope(
|
||||
// import('@strudel/core'),
|
||||
// import('@strudel/draw'),
|
||||
// import('@strudel/mini'),
|
||||
// import('@strudel/tonal'),
|
||||
// import('@strudel/webaudio'),
|
||||
// );
|
||||
// await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]);
|
||||
// },
|
||||
// });
|
||||
//
|
||||
// document.getElementById('play').addEventListener('click', () => editor.evaluate());
|
||||
// document.getElementById('stop').addEventListener('click', () => editor.stop());
|
||||
|
||||
const test = () => {
|
||||
const activeLeaf = GlobalStore.getInstance()
|
||||
.app.workspace.getLeavesOfType('markdown')
|
||||
.find(
|
||||
(leaf: WorkspaceLeaf) =>
|
||||
(leaf.view as MarkdownView).file?.path === props.strudelBlock.filePath
|
||||
)
|
||||
|
||||
const view = activeLeaf?.view as MarkdownView
|
||||
|
||||
if (activeLeaf && view.editor) {
|
||||
console.log('found editor for strudel block')
|
||||
// updateMiniLocations((view.editor as any).cm as EditorView, [])
|
||||
}
|
||||
}
|
||||
|
||||
const play = () => {
|
||||
GlobalStore.getInstance().play(props.strudelBlock)
|
||||
}
|
||||
|
||||
const stop = () => {
|
||||
GlobalStore.getInstance().stop()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const drawContext = canvas.value.getContext('2d')
|
||||
|
||||
props.strudelBlock.setDrawContext(drawContext)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stop()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.strudel-block .strudel-block__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.strudel-block canvas {
|
||||
width: var(--file-line-width);
|
||||
height: 80px;
|
||||
}
|
||||
</style>
|
||||
13
src/components/Views.vue
Normal file
13
src/components/Views.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<Teleport v-for="block in strudelBlocks" :key="block.id" :to="`[data-strudel-id='${block.id}']`">
|
||||
<StrudelBlockView :strudel-block="block as Strudel" />
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GlobalStore } from '@/stores/GlobalStore'
|
||||
import { Strudel } from '@/entities/Strudel'
|
||||
import StrudelBlockView from './StrudelBlockView.vue'
|
||||
|
||||
const { strudelBlocks } = GlobalStore.getInstance()
|
||||
</script>
|
||||
92
src/components/obsidian/Icon.vue
Normal file
92
src/components/obsidian/Icon.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div
|
||||
class="strudel-obsidian-icon"
|
||||
:class="{
|
||||
'strudel-obsidian-icon_with-bg': withBg,
|
||||
'strudel-obsidian-icon_no-hover': noHover,
|
||||
}"
|
||||
@click="emit('click', $event)"
|
||||
>
|
||||
<div v-if="textLeft" class="strudel-obsidian-icon__text">{{ textLeft }}</div>
|
||||
<div v-if="icon" ref="iconEl" class="strudel-obsidian-icon__icon" />
|
||||
<div v-if="textRight" class="strudel-obsidian-icon__text">{{ textRight }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { setIcon } from 'obsidian'
|
||||
|
||||
const props = defineProps<{
|
||||
icon?: string
|
||||
textLeft?: string
|
||||
textRight?: string
|
||||
withBg?: boolean
|
||||
noHover?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent]
|
||||
}>()
|
||||
|
||||
const iconEl = ref<HTMLElement>()
|
||||
|
||||
const updateIcon = () => {
|
||||
if (iconEl.value) {
|
||||
iconEl.value.empty()
|
||||
if (props.icon) {
|
||||
setIcon(iconEl.value, props.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(updateIcon)
|
||||
watch(() => props.icon, updateIcon)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.strudel-obsidian-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 1.5em;
|
||||
color: var(--icon-color);
|
||||
padding: var(--size-2-1) var(--size-2-2);
|
||||
border-radius: var(--radius-s);
|
||||
|
||||
&_with-bg {
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
&:hover {
|
||||
cursor: var(--cursor-link);
|
||||
color: var(--text-normal);
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
&_no-hover {
|
||||
&:hover {
|
||||
cursor: default;
|
||||
color: var(--icon-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.strudel-obsidian-icon__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.strudel-obsidian-icon__text {
|
||||
user-select: none;
|
||||
font-size: var(--font-smaller);
|
||||
|
||||
&:first-child {
|
||||
margin-right: var(--size-2-2);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: var(--size-2-2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
src/constants/keywords.ts
Normal file
1
src/constants/keywords.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const STRUDEL_CODEBLOCK_KEYWORD = 'strudel'
|
||||
58
src/editor/StrudelHeaderWidget.ts
Normal file
58
src/editor/StrudelHeaderWidget.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { WidgetType } from '@codemirror/view'
|
||||
import { genid } from '@/helpers/vueUtils'
|
||||
import { GlobalStore } from '@/stores/GlobalStore'
|
||||
import { Strudel } from '@/entities/Strudel'
|
||||
|
||||
export class StrudelHeaderWidget extends WidgetType {
|
||||
private instance: Strudel
|
||||
|
||||
constructor(code: string, filePath: string, lineFrom: number) {
|
||||
super()
|
||||
|
||||
this.instance = new Strudel({
|
||||
id: genid(),
|
||||
code,
|
||||
filePath,
|
||||
lineFrom,
|
||||
})
|
||||
}
|
||||
|
||||
getInstance() {
|
||||
return this.instance
|
||||
}
|
||||
|
||||
// should be called before any DOM updates
|
||||
updateInstance(newInstance: Strudel) {
|
||||
this.instance = newInstance
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
const container = document.createElement('div')
|
||||
container.id = this.instance.id
|
||||
|
||||
container.createDiv({ attr: { 'data-strudel-id': this.instance.id } })
|
||||
|
||||
if (!GlobalStore.getInstance().strudelBlocks.value.find((t) => t.id === this.instance.id)) {
|
||||
GlobalStore.getInstance().strudelBlocks.value.push(this.instance)
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
destroy() {
|
||||
const store = GlobalStore.getInstance()
|
||||
const index = store.strudelBlocks.value.findIndex((t) => t.id === this.instance.id)
|
||||
if (index !== -1) {
|
||||
// store.strudelBlocks.value[index].cleanup()
|
||||
store.strudelBlocks.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
eq(other: StrudelHeaderWidget) {
|
||||
return this.instance.compare(other.instance)
|
||||
}
|
||||
|
||||
ignoreEvent() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
164
src/editor/StrudelHighlight.ts
Normal file
164
src/editor/StrudelHighlight.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { RangeSetBuilder, StateEffect, StateField, Prec } from '@codemirror/state'
|
||||
import { Decoration, DecorationSet, EditorView } from '@codemirror/view'
|
||||
|
||||
export const setMiniLocations = StateEffect.define<{
|
||||
locations: Array<[number, number]>
|
||||
shift: number
|
||||
}>()
|
||||
export const showMiniLocations = StateEffect.define<{
|
||||
atTime: number
|
||||
haps: any[]
|
||||
}>()
|
||||
export const updateMiniLocations = (
|
||||
view: EditorView,
|
||||
locations: Array<[number, number]>,
|
||||
shift: number
|
||||
) => {
|
||||
view.dispatch({ effects: setMiniLocations.of({ locations, shift }) })
|
||||
}
|
||||
export const highlightMiniLocations = (view: EditorView, atTime: number, haps: any[]) => {
|
||||
view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) })
|
||||
}
|
||||
|
||||
const miniLocations = StateField.define<DecorationSet>({
|
||||
create() {
|
||||
return Decoration.none
|
||||
},
|
||||
update(locations, tr) {
|
||||
if (tr.docChanged) {
|
||||
locations = locations.map(tr.changes)
|
||||
}
|
||||
|
||||
// console.log('miniLocations update', tr.effects)
|
||||
|
||||
for (const e of tr.effects) {
|
||||
if (e.is(setMiniLocations)) {
|
||||
// this is called on eval, with the mini locations obtained from the transpiler
|
||||
// codemirror will automatically remap the marks when the document is edited
|
||||
// create a mark for each mini location, adding the range to the spec to find it later
|
||||
const marks = e.value.locations
|
||||
.filter(([from]) => from < tr.newDoc.length)
|
||||
.map(([from, to]) => [from, Math.min(to, tr.newDoc.length)])
|
||||
.map(
|
||||
(range) =>
|
||||
Decoration.mark({
|
||||
id: range.join(':'),
|
||||
// this green is only to verify that the decoration moves when the document is edited
|
||||
// it will be removed later, so the mark is not visible by default
|
||||
// attributes: { style: `background-color: #00CA2880` },
|
||||
}).range(range[0] + e.value.shift, range[1] + e.value.shift) // -> Decoration
|
||||
)
|
||||
|
||||
locations = Decoration.set(marks, true) // -> DecorationSet === RangeSet<Decoration>
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
},
|
||||
})
|
||||
|
||||
const visibleMiniLocations = StateField.define<{
|
||||
atTime: number
|
||||
haps: Map<string, any>
|
||||
}>({
|
||||
create() {
|
||||
return { atTime: 0, haps: new Map(), shift: 0 }
|
||||
},
|
||||
update(visible, tr) {
|
||||
for (const e of tr.effects) {
|
||||
if (e.is(showMiniLocations)) {
|
||||
// this is called every frame to show the locations that are currently active
|
||||
// we can NOT create new marks because the context.locations haven't changed since eval time
|
||||
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
|
||||
const haps = new Map()
|
||||
// console.log(e.value, e.value.haps)
|
||||
for (const hap of e.value.haps) {
|
||||
if (!hap.context?.locations || !hap.whole) {
|
||||
continue
|
||||
}
|
||||
for (const { start, end } of hap.context.locations) {
|
||||
// start += e.value.shift
|
||||
// end += e.value.shift
|
||||
const id = `${start}:${end}`
|
||||
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
|
||||
haps.set(id, hap)
|
||||
}
|
||||
}
|
||||
}
|
||||
visible = { atTime: e.value.atTime, haps }
|
||||
}
|
||||
}
|
||||
|
||||
return visible
|
||||
},
|
||||
})
|
||||
|
||||
// // Derive the set of decorations from the miniLocations and visibleLocations
|
||||
const miniLocationHighlights = EditorView.decorations.compute(
|
||||
[miniLocations, visibleMiniLocations],
|
||||
(state) => {
|
||||
const iterator = state.field(miniLocations).iter()
|
||||
const { haps } = state.field(visibleMiniLocations)
|
||||
const builder = new RangeSetBuilder<Decoration>()
|
||||
|
||||
// console.log('highlighting mini locations', haps, shift)
|
||||
|
||||
while (iterator.value) {
|
||||
const {
|
||||
from,
|
||||
to,
|
||||
value: {
|
||||
spec: { id },
|
||||
},
|
||||
} = iterator
|
||||
|
||||
// console.log(haps, id)
|
||||
|
||||
if (haps.has(id)) {
|
||||
const hap = haps.get(id)
|
||||
const colorMod = hap.value?.color ? ` strudel-mark_${hap.value.color}` : ''
|
||||
// const style = hap.value?.markcss || `outline: solid 2px ${color}`
|
||||
const classStr = `strudel-mark${colorMod}`
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
swatch.style.color = color;
|
||||
document.body.appendChild(swatch);
|
||||
let channels = getComputedStyle(swatch)
|
||||
.color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/)
|
||||
.slice(1)
|
||||
.map((c) => parseFloat(c || 1));
|
||||
document.body.removeChild(swatch);
|
||||
|
||||
// Get percentage of event
|
||||
const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration;
|
||||
channels[3] *= percent;
|
||||
*/
|
||||
|
||||
builder.add(
|
||||
from,
|
||||
to,
|
||||
Decoration.mark({
|
||||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
|
||||
attributes: { class: classStr },
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
iterator.next()
|
||||
}
|
||||
|
||||
return builder.finish()
|
||||
}
|
||||
)
|
||||
|
||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights]
|
||||
|
||||
// export const isPatternHighlightingEnabled = (on, config) => {
|
||||
// on &&
|
||||
// config &&
|
||||
// setTimeout(() => {
|
||||
// updateMiniLocations(config.editor, config.miniLocations)
|
||||
// }, 100)
|
||||
// return on ? Prec.highest(highlightExtension) : []
|
||||
// }
|
||||
97
src/editor/StrudelPlugin.ts
Normal file
97
src/editor/StrudelPlugin.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { StateField, RangeSetBuilder, EditorState } from '@codemirror/state'
|
||||
import { Decoration, DecorationSet, EditorView } from '@codemirror/view'
|
||||
import { editorLivePreviewField, editorInfoField } from 'obsidian'
|
||||
import { StrudelHeaderWidget } from './StrudelHeaderWidget'
|
||||
import { STRUDEL_CODEBLOCK_KEYWORD } from '@/constants/keywords'
|
||||
import { GlobalStore } from '@/stores/GlobalStore'
|
||||
|
||||
function buildStrudelDecorations(state: EditorState): DecorationSet {
|
||||
const builder = new RangeSetBuilder<Decoration>()
|
||||
|
||||
if (!state.field(editorLivePreviewField)) {
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
const currentFile = state.field(editorInfoField)?.file
|
||||
if (!currentFile) {
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
for (let i = 1; i <= state.doc.lines; i++) {
|
||||
const line = state.doc.line(i)
|
||||
|
||||
if (line.text.trim() === `\`\`\`${STRUDEL_CODEBLOCK_KEYWORD}`) {
|
||||
const codeLines = []
|
||||
let endLineFound = false
|
||||
|
||||
// find the end of the code block and collect code lines
|
||||
let endLineNumber = i + 1
|
||||
while (endLineNumber <= state.doc.lines) {
|
||||
const endLine = state.doc.line(endLineNumber)
|
||||
if (endLine.text.trim() === '```') {
|
||||
endLineFound = true
|
||||
break
|
||||
}
|
||||
codeLines.push(endLine.text)
|
||||
endLineNumber++
|
||||
}
|
||||
|
||||
if (!endLineFound) {
|
||||
continue // skip if no closing ```
|
||||
}
|
||||
|
||||
const code = codeLines.join('\n')
|
||||
|
||||
if (!code.trim()) {
|
||||
continue // skip empty code blocks
|
||||
}
|
||||
|
||||
const widget = new StrudelHeaderWidget(code, currentFile.path, state.doc.line(i + 1).from)
|
||||
|
||||
// searching for existing instance
|
||||
const existingInstance = GlobalStore.getInstance().strudelBlocks.value.find((s) =>
|
||||
s.compare(widget.getInstance())
|
||||
)
|
||||
|
||||
if (existingInstance) {
|
||||
existingInstance.code = code
|
||||
existingInstance.lineFrom = state.doc.line(i + 1).from
|
||||
// update instance at newly created widget
|
||||
widget.updateInstance(existingInstance)
|
||||
}
|
||||
|
||||
// add widget at the start of the code block
|
||||
builder.add(
|
||||
line.from,
|
||||
line.from,
|
||||
Decoration.widget({
|
||||
widget,
|
||||
block: true,
|
||||
side: -1,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
export const strudelStateField = StateField.define<DecorationSet>({
|
||||
create(state) {
|
||||
return buildStrudelDecorations(state)
|
||||
},
|
||||
update(decorations, tr) {
|
||||
if (
|
||||
tr.docChanged ||
|
||||
tr.selection ||
|
||||
tr.state.field(editorLivePreviewField) !== tr.startState.field(editorLivePreviewField)
|
||||
) {
|
||||
return buildStrudelDecorations(tr.state)
|
||||
}
|
||||
|
||||
return decorations.map(tr.changes)
|
||||
},
|
||||
provide(field) {
|
||||
return [EditorView.decorations.from(field)]
|
||||
},
|
||||
})
|
||||
1149
src/editor/SyntaxHighlighting.js
Normal file
1149
src/editor/SyntaxHighlighting.js
Normal file
File diff suppressed because it is too large
Load diff
35
src/entities/Strudel.ts
Normal file
35
src/entities/Strudel.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
export class Strudel {
|
||||
public id: string
|
||||
public code: string
|
||||
public filePath: string
|
||||
public lineFrom: number
|
||||
public drawContext: CanvasRenderingContext2D | null = null
|
||||
|
||||
public lineFromWhenPlaybackStarts: number | null = null
|
||||
|
||||
constructor(data: { id: string; code: string; filePath: string; lineFrom: number }) {
|
||||
this.id = data.id
|
||||
this.code = data.code
|
||||
this.filePath = data.filePath
|
||||
this.lineFrom = data.lineFrom
|
||||
}
|
||||
|
||||
compare(other: Strudel) {
|
||||
return (
|
||||
(this.code === other.code || this.lineFrom === other.lineFrom) &&
|
||||
this.filePath === other.filePath
|
||||
)
|
||||
}
|
||||
|
||||
setDrawContext(ctx: CanvasRenderingContext2D) {
|
||||
this.drawContext = ctx
|
||||
}
|
||||
|
||||
playbackStarted() {
|
||||
this.lineFromWhenPlaybackStarts = this.lineFrom
|
||||
}
|
||||
|
||||
playbackStopped() {
|
||||
this.lineFromWhenPlaybackStarts = null
|
||||
}
|
||||
}
|
||||
3
src/global.d.ts
vendored
Normal file
3
src/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare module '@/strudel/init.js'
|
||||
declare module '@/strudel/codemirror/highlight.mjs'
|
||||
declare module '@/strudel/draw/index.mjs'
|
||||
116
src/helpers/vueUtils.ts
Normal file
116
src/helpers/vueUtils.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { nanoid } from 'nanoid'
|
||||
import { App } from 'obsidian'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp, defineComponent, Component as VueComponent } from 'vue'
|
||||
|
||||
/**
|
||||
* Helper class for managing Vue components in Obsidian
|
||||
*/
|
||||
export class VueRenderer {
|
||||
private static instance: VueRenderer
|
||||
private app: App
|
||||
private vueApps: Map<string, any> = new Map()
|
||||
|
||||
private constructor(app: App) {
|
||||
this.app = app
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of VueRenderer
|
||||
* @param app Obsidian App instance
|
||||
* @returns VueRenderer instance
|
||||
*/
|
||||
public static getInstance(app: App): VueRenderer {
|
||||
if (!VueRenderer.instance) {
|
||||
VueRenderer.instance = new VueRenderer(app)
|
||||
}
|
||||
return VueRenderer.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount a Vue component to a DOM element
|
||||
* @param container The DOM element to mount the component to
|
||||
* @param component The Vue component to mount
|
||||
* @param props Props to pass to the component
|
||||
* @returns A unique ID for the mounted component
|
||||
*/
|
||||
public mountComponent(
|
||||
container: HTMLElement,
|
||||
component: VueComponent,
|
||||
props: Record<string, any> = {}
|
||||
): string {
|
||||
// Create a unique ID for this component instance
|
||||
const id = `vue-component-${Date.now()}-${Math.floor(Math.random() * 10000)}`
|
||||
|
||||
// Create a wrapper div for the Vue app
|
||||
const mountPoint = document.createElement('div')
|
||||
mountPoint.id = id
|
||||
container.appendChild(mountPoint)
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
// Create a Vue app with the component
|
||||
const app = createApp(component, props)
|
||||
|
||||
app.use(pinia)
|
||||
|
||||
// Mount the app to the container
|
||||
app.mount(`#${id}`)
|
||||
|
||||
// Store the app reference for cleanup
|
||||
this.vueApps.set(id, app)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmount a Vue component by its ID
|
||||
* @param id The ID of the component to unmount
|
||||
*/
|
||||
public unmountComponent(id: string): void {
|
||||
const app = this.vueApps.get(id)
|
||||
if (app) {
|
||||
app.unmount()
|
||||
this.vueApps.delete(id)
|
||||
const mountPoint = document.getElementById(id)
|
||||
if (mountPoint) {
|
||||
mountPoint.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a Vue component that can be used with Obsidian's Component system
|
||||
* @param component The Vue component to wrap
|
||||
* @param props Props to pass to the component
|
||||
* @returns A function that can be used to mount the component
|
||||
*/
|
||||
public createComponentWrapper(
|
||||
component: VueComponent,
|
||||
props: Record<string, any> = {}
|
||||
): (container: HTMLElement) => string {
|
||||
return (container: HTMLElement) => {
|
||||
return this.mountComponent(container, component, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple Vue component from a template string
|
||||
* @param template The Vue template string
|
||||
* @param options Additional Vue component options
|
||||
* @returns A Vue component
|
||||
*/
|
||||
export function createVueComponent(
|
||||
template: string,
|
||||
options: Record<string, any> = {}
|
||||
): VueComponent {
|
||||
return defineComponent({
|
||||
template,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export function genid(): string {
|
||||
return `vue-${nanoid()}`
|
||||
}
|
||||
119
src/main.ts
Normal file
119
src/main.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { MarkdownPostProcessorContext, Plugin } from 'obsidian'
|
||||
import './styles.css'
|
||||
import { GlobalStore } from './stores/GlobalStore'
|
||||
import { createApp, App as VueApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import VueEntry from './App.vue'
|
||||
import { StrudelConfig } from './services/StrudelConfig'
|
||||
import { strudelStateField } from './editor/StrudelPlugin'
|
||||
import { highlightExtension } from './editor/StrudelHighlight'
|
||||
// import { STRUDEL_CODEBLOCK_KEYWORD } from './constants/keywords'
|
||||
// import { StrudelRenderer } from './editor/StrudelRenderer'
|
||||
// import { highlightExtension } from '@/strudel/codemirror/highlight.mjs'
|
||||
|
||||
import './editor/SyntaxHighlighting.js'
|
||||
import { createStrudelBlock } from './commands/createStrudelBlock'
|
||||
|
||||
export interface StrudelSettings {}
|
||||
|
||||
const DEFAULT_SETTINGS: StrudelSettings = {}
|
||||
|
||||
interface PluginData {}
|
||||
|
||||
export default class StrudelPlugin extends Plugin {
|
||||
settings: StrudelSettings
|
||||
strudelConfig: StrudelConfig
|
||||
private data: PluginData = {}
|
||||
|
||||
private vueApp: VueApp | null = null
|
||||
|
||||
initializeVue() {
|
||||
const rootContainer = document.createElement('div')
|
||||
rootContainer.id = 'strudel-vue-root'
|
||||
rootContainer.style.display = 'none'
|
||||
document.body.appendChild(rootContainer)
|
||||
|
||||
this.vueApp = createApp(VueEntry)
|
||||
this.vueApp.use(createPinia())
|
||||
this.vueApp.mount(rootContainer)
|
||||
}
|
||||
|
||||
getVueApp(): VueApp {
|
||||
return this.vueApp!
|
||||
}
|
||||
|
||||
async onload() {
|
||||
await this.loadPluginData()
|
||||
await this.loadSettings()
|
||||
|
||||
GlobalStore.getInstance().init(this.app)
|
||||
|
||||
// this.addSettingTab(new StrudelSettingTab(this.app, this))
|
||||
|
||||
console.log('Strudel REPL Plugin loaded.')
|
||||
|
||||
this.initializeVue()
|
||||
|
||||
this.registerEditorExtension(strudelStateField)
|
||||
this.registerEditorExtension(highlightExtension)
|
||||
|
||||
GlobalStore.getInstance().initStrudel()
|
||||
|
||||
// this.addCommand({
|
||||
// id: 'create-strudel-block',
|
||||
// name: 'Create new strudel block',
|
||||
// callback: () => {
|
||||
// createStrudelBlock()
|
||||
// },
|
||||
// })
|
||||
}
|
||||
|
||||
onunload() {
|
||||
GlobalStore.getInstance().destroy()
|
||||
if (this.vueApp) {
|
||||
this.vueApp.unmount()
|
||||
document.getElementById('strudel-vue-root')?.remove()
|
||||
}
|
||||
console.log('Strudel REPL plugin unloaded.')
|
||||
}
|
||||
|
||||
applySettings() {
|
||||
this.strudelConfig = StrudelConfig.getInstance()
|
||||
// this.strudelConfig.refreshDelay = this.settings.refreshDelay
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
|
||||
this.applySettings()
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings)
|
||||
this.applySettings()
|
||||
}
|
||||
|
||||
async loadPluginData() {
|
||||
this.data = (await this.loadData()) || {}
|
||||
}
|
||||
|
||||
async savePluginData() {
|
||||
await this.saveData(this.data)
|
||||
}
|
||||
}
|
||||
|
||||
// class StrudelSettingTab extends PluginSettingTab {
|
||||
// plugin: StrudelPlugin
|
||||
//
|
||||
// constructor(app: App, plugin: StrudelPlugin) {
|
||||
// super(app, plugin)
|
||||
// this.plugin = plugin
|
||||
// }
|
||||
//
|
||||
// display(): void {
|
||||
// const { containerEl } = this
|
||||
//
|
||||
// containerEl.empty()
|
||||
//
|
||||
// containerEl.createEl('h2', { text: 'Strudel REPL Settings' })
|
||||
// }
|
||||
// }
|
||||
13
src/services/StrudelConfig.ts
Normal file
13
src/services/StrudelConfig.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export class StrudelConfig {
|
||||
private static instance: StrudelConfig
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): StrudelConfig {
|
||||
if (!StrudelConfig.instance) {
|
||||
StrudelConfig.instance = new StrudelConfig()
|
||||
}
|
||||
return StrudelConfig.instance
|
||||
}
|
||||
}
|
||||
189
src/stores/GlobalStore.ts
Normal file
189
src/stores/GlobalStore.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import { Strudel } from '@/entities/Strudel'
|
||||
import { App } from 'obsidian'
|
||||
import { ref } from 'vue'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { MarkdownView, WorkspaceLeaf } from 'obsidian'
|
||||
import { initStrudel as init, recalculateMiniLocations } from '@/strudel/init.js'
|
||||
import { highlightMiniLocations, updateMiniLocations } from '@/editor/StrudelHighlight'
|
||||
import { Drawer, drawPianoroll } from '@/strudel/draw/index.mjs'
|
||||
|
||||
export class GlobalStore {
|
||||
private static instance: GlobalStore
|
||||
private _app: App
|
||||
|
||||
public repl: any
|
||||
public readonly strudelInitialized = ref(false)
|
||||
|
||||
public readonly initialized = ref(false)
|
||||
public readonly currentFile = ref(null)
|
||||
|
||||
public readonly strudelBlocks = ref<Strudel[]>([])
|
||||
|
||||
public static getInstance(): GlobalStore {
|
||||
if (!GlobalStore.instance) {
|
||||
GlobalStore.instance = new GlobalStore()
|
||||
}
|
||||
return GlobalStore.instance
|
||||
}
|
||||
|
||||
public get app(): App {
|
||||
return this._app
|
||||
}
|
||||
|
||||
public getActiveEditor() {
|
||||
const activeLeaf = GlobalStore.getInstance()
|
||||
.app.workspace.getLeavesOfType('markdown')
|
||||
.find(
|
||||
(leaf: WorkspaceLeaf) =>
|
||||
(leaf.view as MarkdownView).file?.path === this.currentBlock.value?.filePath
|
||||
)
|
||||
|
||||
const view = activeLeaf?.view as MarkdownView
|
||||
|
||||
if (activeLeaf && view.editor) {
|
||||
return (view.editor as any).cm as EditorView
|
||||
}
|
||||
}
|
||||
|
||||
public readonly currentBlock = ref<Strudel | null>(null)
|
||||
public readonly isPlaying = ref(false)
|
||||
|
||||
public play(strudelBlock: Strudel) {
|
||||
this.currentBlock.value = strudelBlock
|
||||
this.repl.evaluate(strudelBlock.code)
|
||||
this.isPlaying.value = true
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.repl.stop()
|
||||
this.isPlaying.value = false
|
||||
}
|
||||
|
||||
public drawer: any | null = null
|
||||
|
||||
// public resetMiniLocations() {
|
||||
// const editor = this.getActiveEditor()
|
||||
// if (this.currentBlock.value && editor) {
|
||||
// updateMiniLocations(this.getActiveEditor(), [])
|
||||
// }
|
||||
// }
|
||||
|
||||
// public recalculateMiniLocations() {
|
||||
// if (this.currentBlock.value) {
|
||||
// const locations = recalculateMiniLocations(this.currentBlock.value.code)
|
||||
// updateMiniLocations(this.getActiveEditor(), locations || [])
|
||||
// }
|
||||
// }
|
||||
|
||||
public async initStrudel() {
|
||||
this.repl = await init({
|
||||
afterEval: (options: any) => {
|
||||
// console.log('afterEval', options.meta?.miniLocations)
|
||||
if (options.meta?.miniLocations) {
|
||||
const locations = options.meta.miniLocations
|
||||
// for (const loc of locations) {
|
||||
// loc[0] = loc[0] + this.currentBlock.lineFrom
|
||||
// loc[1] = loc[1] + this.currentBlock.lineFrom
|
||||
// }
|
||||
const editor = this.getActiveEditor()
|
||||
if (this.currentBlock.value && editor) {
|
||||
this.currentBlock.value.playbackStarted()
|
||||
updateMiniLocations(editor, locations || [], this.currentBlock.value.lineFrom)
|
||||
}
|
||||
}
|
||||
|
||||
const drawTime = [-2, 2]
|
||||
this.drawer.setDrawTime(drawTime)
|
||||
// invalidate drawer after we've set the appropriate drawTime
|
||||
this.drawer.invalidate(this.repl.scheduler)
|
||||
},
|
||||
onToggle: (started: boolean) => {
|
||||
console.log('onToggle', started)
|
||||
if (started) {
|
||||
this.currentBlock.value.playbackStarted()
|
||||
this.drawer.start(this.repl.scheduler)
|
||||
// if (this.solo) {
|
||||
// // stop other repls when this one is started
|
||||
// document.dispatchEvent(
|
||||
// new CustomEvent('start-repl', {
|
||||
// detail: this.id,
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
} else {
|
||||
this.drawer.stop()
|
||||
|
||||
const editor = this.getActiveEditor()
|
||||
console.log('playback stopped')
|
||||
if (editor) {
|
||||
this.currentBlock.value?.playbackStopped()
|
||||
console.log('clearing mini locations')
|
||||
updateMiniLocations(editor, [], 0)
|
||||
}
|
||||
|
||||
this.currentBlock.value = null
|
||||
}
|
||||
},
|
||||
})
|
||||
console.log(this.repl)
|
||||
this.strudelInitialized.value = true
|
||||
|
||||
this.drawer = new Drawer(
|
||||
(haps: any, time: any, _: any) => {
|
||||
const editor = this.getActiveEditor()
|
||||
if (!editor || !this.currentBlock.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// console.log(this.currentBlock.value.lineFrom)
|
||||
const currentFrame = haps.filter((hap: any) => hap.isActive(time))
|
||||
highlightMiniLocations(editor, time, currentFrame)
|
||||
|
||||
const rootStyles = getComputedStyle(document.getElementsByTagName('body')[0])
|
||||
const playheadColor = rootStyles.getPropertyValue('--text-accent').trim()
|
||||
const inactiveColor = rootStyles
|
||||
.getPropertyValue('--background-modifier-active-hover')
|
||||
.trim()
|
||||
const activeColor = rootStyles.getPropertyValue('--text-accent').trim()
|
||||
|
||||
const drawContext = this.currentBlock.value?.drawContext
|
||||
if (this.currentBlock.value?.drawContext) {
|
||||
drawPianoroll({
|
||||
haps,
|
||||
time,
|
||||
ctx: drawContext,
|
||||
drawTime: [-2, 2],
|
||||
fold: 1,
|
||||
// labels: true,
|
||||
playheadColor,
|
||||
inactive: inactiveColor,
|
||||
active: activeColor,
|
||||
fillActive: true,
|
||||
strokeActive: false,
|
||||
})
|
||||
}
|
||||
},
|
||||
[-2, 2]
|
||||
)
|
||||
}
|
||||
|
||||
public init(app: App): void {
|
||||
if (this.initialized.value) {
|
||||
return
|
||||
}
|
||||
|
||||
this._app = app
|
||||
|
||||
this.initialized.value = true
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (!this.initialized.value) {
|
||||
return
|
||||
}
|
||||
|
||||
this.initialized.value = false
|
||||
this.currentFile.value = null
|
||||
this.strudelBlocks.value.splice(0, this.strudelBlocks.value.length)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# @strudel/codemirror
|
||||
|
||||
This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it.
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
import jsdoc from '../../doc.json';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
import { h } from './html';
|
||||
import { Scale } from '@tonaljs/tonal';
|
||||
import { soundMap } from 'superdough';
|
||||
import { complex } from '@strudel/tonal';
|
||||
|
||||
const escapeHtml = (str) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerText = str;
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
const stripHtml = (html) => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
|
||||
const buildParamsList = (params) =>
|
||||
params?.length
|
||||
? `
|
||||
<div class="autocomplete-info-params-section">
|
||||
<h4 class="autocomplete-info-section-title">Parameters</h4>
|
||||
<ul class="autocomplete-info-params-list">
|
||||
${params
|
||||
.map(
|
||||
({ name, type, description }) => `
|
||||
<li class="autocomplete-info-param-item">
|
||||
<span class="autocomplete-info-param-name">${name}</span>
|
||||
<span class="autocomplete-info-param-type">${type.names?.join(' | ')}</span>
|
||||
${description ? `<div class="autocomplete-info-param-desc">${stripHtml(description)}</div>` : ''}
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
const buildExamples = (examples) =>
|
||||
examples?.length
|
||||
? `
|
||||
<div class="autocomplete-info-examples-section">
|
||||
<h4 class="autocomplete-info-section-title">Examples</h4>
|
||||
${examples
|
||||
.map(
|
||||
(example) => `
|
||||
<pre class="autocomplete-info-example-code">${escapeHtml(example)}</pre>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
export const Autocomplete = (doc) =>
|
||||
h`
|
||||
<div class="autocomplete-info-container">
|
||||
<div class="autocomplete-info-tooltip">
|
||||
<h3 class="autocomplete-info-function-name">${getDocLabel(doc)}</h3>
|
||||
${doc.synonyms_text ? `<div class="autocomplete-info-function-synonyms">Synonyms: ${doc.synonyms_text}</div>` : ''}
|
||||
${doc.description ? `<div class="autocomplete-info-function-description">${doc.description}</div>` : ''}
|
||||
${buildParamsList(doc.params)}
|
||||
${buildExamples(doc.examples)}
|
||||
</div>
|
||||
</div>
|
||||
`[0];
|
||||
|
||||
const isValidDoc = (doc) => {
|
||||
const label = getDocLabel(doc);
|
||||
return label && !label.startsWith('_') && !['package'].includes(doc.kind);
|
||||
};
|
||||
|
||||
const hasExcludedTags = (doc) =>
|
||||
['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag));
|
||||
|
||||
export function bankCompletions() {
|
||||
const soundDict = soundMap.get();
|
||||
const banks = new Set();
|
||||
for (const key of Object.keys(soundDict)) {
|
||||
const [bank, suffix] = key.split('_');
|
||||
if (suffix && bank) banks.add(bank);
|
||||
}
|
||||
return Array.from(banks)
|
||||
.sort()
|
||||
.map((name) => ({ label: name, type: 'bank' }));
|
||||
}
|
||||
|
||||
// Attempt to get all scale names from Tonal
|
||||
let scaleCompletions = [];
|
||||
try {
|
||||
scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
|
||||
} catch (e) {
|
||||
console.warn('[autocomplete] Could not load scale names from Tonal:', e);
|
||||
}
|
||||
|
||||
// Valid mode values for voicing
|
||||
const modeCompletions = [
|
||||
{ label: 'below', type: 'mode' },
|
||||
{ label: 'above', type: 'mode' },
|
||||
{ label: 'duck', type: 'mode' },
|
||||
{ label: 'root', type: 'mode' },
|
||||
];
|
||||
|
||||
// Valid chord symbols from ireal dictionary plus empty string for major triads
|
||||
const chordSymbols = ['', ...Object.keys(complex)].sort();
|
||||
const chordSymbolCompletions = chordSymbols.map((symbol) => {
|
||||
if (symbol === '') {
|
||||
return {
|
||||
label: 'major',
|
||||
apply: '',
|
||||
type: 'chord-symbol',
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: symbol,
|
||||
apply: symbol,
|
||||
type: 'chord-symbol',
|
||||
};
|
||||
});
|
||||
|
||||
export const getSynonymDoc = (doc, synonym) => {
|
||||
const synonyms = doc.synonyms || [];
|
||||
const docLabel = getDocLabel(doc);
|
||||
// Swap `doc.name` in for `s` in the list of synonyms
|
||||
const synonymsWithDoc = [docLabel, ...synonyms].filter((x) => x && x !== synonym);
|
||||
return {
|
||||
...doc,
|
||||
name: synonym,
|
||||
longname: synonym,
|
||||
synonyms: synonymsWithDoc,
|
||||
synonyms_text: synonymsWithDoc.join(', '),
|
||||
};
|
||||
};
|
||||
|
||||
const jsdocCompletions = (() => {
|
||||
const seen = new Set(); // avoid repetition
|
||||
const completions = [];
|
||||
for (const doc of jsdoc.docs) {
|
||||
if (!isValidDoc(doc) || hasExcludedTags(doc)) continue;
|
||||
const docLabel = getDocLabel(doc);
|
||||
// Remove duplicates
|
||||
const synonyms = doc.synonyms || [];
|
||||
let labels = [docLabel, ...synonyms];
|
||||
for (const label of labels) {
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
if (label && !seen.has(label)) {
|
||||
seen.add(label);
|
||||
completions.push({
|
||||
label,
|
||||
info: () => Autocomplete(getSynonymDoc(doc, label)),
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return completions;
|
||||
})();
|
||||
|
||||
// --- Handler functions for each context ---
|
||||
const pitchNames = [
|
||||
'C',
|
||||
'C#',
|
||||
'Db',
|
||||
'D',
|
||||
'D#',
|
||||
'Eb',
|
||||
'E',
|
||||
'E#',
|
||||
'Fb',
|
||||
'F',
|
||||
'F#',
|
||||
'Gb',
|
||||
'G',
|
||||
'G#',
|
||||
'Ab',
|
||||
'A',
|
||||
'A#',
|
||||
'Bb',
|
||||
'B',
|
||||
'B#',
|
||||
'Cb',
|
||||
];
|
||||
|
||||
// Cached regex patterns for scaleHandler
|
||||
const SCALE_NO_QUOTES_REGEX = /scale\(\s*$/;
|
||||
const SCALE_AFTER_COLON_REGEX = /scale\(\s*['"][^'"]*:[^'"]*$/;
|
||||
const SCALE_PRE_COLON_REGEX = /scale\(\s*['"][^'"]*$/;
|
||||
const SCALE_PITCH_MATCH_REGEX = /([A-Ga-g][#b]*)?$/;
|
||||
const SCALE_SPACES_TO_COLON_REGEX = /\s+/g;
|
||||
|
||||
function scaleHandler(context) {
|
||||
// First check for scale context without quotes - block with empty completions
|
||||
let scaleNoQuotesContext = context.matchBefore(SCALE_NO_QUOTES_REGEX);
|
||||
if (scaleNoQuotesContext) {
|
||||
return {
|
||||
from: scaleNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Check for after-colon context first (more specific)
|
||||
let scaleAfterColonContext = context.matchBefore(SCALE_AFTER_COLON_REGEX);
|
||||
if (scaleAfterColonContext) {
|
||||
const text = scaleAfterColonContext.text;
|
||||
const colonIdx = text.lastIndexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const fragment = text.slice(colonIdx + 1);
|
||||
const filteredScales = scaleCompletions.filter((s) => s.label.startsWith(fragment));
|
||||
const options = filteredScales.map((s) => ({
|
||||
...s,
|
||||
apply: s.label.replace(SCALE_SPACES_TO_COLON_REGEX, ':'),
|
||||
}));
|
||||
const from = scaleAfterColonContext.from + colonIdx + 1;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for pre-colon context
|
||||
let scalePreColonContext = context.matchBefore(SCALE_PRE_COLON_REGEX);
|
||||
if (scalePreColonContext) {
|
||||
if (!scalePreColonContext.text.includes(':')) {
|
||||
if (context.explicit) {
|
||||
const text = scalePreColonContext.text;
|
||||
const match = text.match(SCALE_PITCH_MATCH_REGEX);
|
||||
const fragment = match ? match[0] : '';
|
||||
const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const from = scalePreColonContext.to - fragment.length;
|
||||
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
||||
return { from, options };
|
||||
} else {
|
||||
return { from: scalePreColonContext.to, options: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cached regex patterns for soundHandler
|
||||
const SOUND_NO_QUOTES_REGEX = /(s|sound)\(\s*$/;
|
||||
const SOUND_WITH_QUOTES_REGEX = /(s|sound)\(\s*['"][^'"]*$/;
|
||||
const SOUND_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w]*)$/;
|
||||
|
||||
function soundHandler(context) {
|
||||
// First check for sound context without quotes - block with empty completions
|
||||
let soundNoQuotesContext = context.matchBefore(SOUND_NO_QUOTES_REGEX);
|
||||
if (soundNoQuotesContext) {
|
||||
return {
|
||||
from: soundNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for sound context with quotes - provide completions
|
||||
let soundContext = context.matchBefore(SOUND_WITH_QUOTES_REGEX);
|
||||
if (!soundContext) return null;
|
||||
|
||||
const text = soundContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragMatch = inside.match(SOUND_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
const soundNames = Object.keys(soundMap.get()).sort();
|
||||
const filteredSounds = soundNames.filter((name) => name.includes(fragment));
|
||||
let options = filteredSounds.map((name) => ({ label: name, type: 'sound' }));
|
||||
const from = soundContext.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for bankHandler
|
||||
const BANK_NO_QUOTES_REGEX = /bank\(\s*$/;
|
||||
const BANK_WITH_QUOTES_REGEX = /bank\(\s*['"][^'"]*$/;
|
||||
|
||||
function bankHandler(context) {
|
||||
// First check for bank context without quotes - block with empty completions
|
||||
let bankNoQuotesContext = context.matchBefore(BANK_NO_QUOTES_REGEX);
|
||||
if (bankNoQuotesContext) {
|
||||
return {
|
||||
from: bankNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for bank context with quotes - provide completions
|
||||
let bankMatch = context.matchBefore(BANK_WITH_QUOTES_REGEX);
|
||||
if (!bankMatch) return null;
|
||||
|
||||
const text = bankMatch.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragment = inside;
|
||||
let banks = bankCompletions();
|
||||
const filteredBanks = banks.filter((b) => b.label.startsWith(fragment));
|
||||
const from = bankMatch.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options: filteredBanks,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for modeHandler
|
||||
const MODE_NO_QUOTES_REGEX = /mode\(\s*$/;
|
||||
const MODE_AFTER_COLON_REGEX = /mode\(\s*['"][^'"]*:[^'"]*$/;
|
||||
const MODE_PRE_COLON_REGEX = /mode\(\s*['"][^'"]*$/;
|
||||
const MODE_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w:]*)$/;
|
||||
|
||||
function modeHandler(context) {
|
||||
// First check for mode context without quotes - block with empty completions
|
||||
let modeNoQuotesContext = context.matchBefore(MODE_NO_QUOTES_REGEX);
|
||||
if (modeNoQuotesContext) {
|
||||
return {
|
||||
from: modeNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Check for after-colon context first (more specific)
|
||||
let modeAfterColonContext = context.matchBefore(MODE_AFTER_COLON_REGEX);
|
||||
if (modeAfterColonContext) {
|
||||
const text = modeAfterColonContext.text;
|
||||
const colonIdx = text.lastIndexOf(':');
|
||||
if (colonIdx !== -1) {
|
||||
const fragment = text.slice(colonIdx + 1);
|
||||
// For anchor after colon, we can suggest pitch names
|
||||
const filtered = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
||||
const from = modeAfterColonContext.from + colonIdx + 1;
|
||||
return {
|
||||
from,
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for pre-colon context
|
||||
let modeContext = context.matchBefore(MODE_PRE_COLON_REGEX);
|
||||
if (!modeContext) return null;
|
||||
|
||||
const text = modeContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
const fragMatch = inside.match(MODE_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
const filteredModes = modeCompletions.filter((m) => m.label.startsWith(fragment));
|
||||
const from = modeContext.to - fragment.length;
|
||||
return {
|
||||
from,
|
||||
options: filteredModes,
|
||||
};
|
||||
}
|
||||
|
||||
// Cached regex patterns for chordHandler
|
||||
const CHORD_NO_QUOTES_REGEX = /chord\(\s*$/;
|
||||
const CHORD_WITH_QUOTES_REGEX = /chord\(\s*['"][^'"]*$/;
|
||||
const CHORD_FRAGMENT_MATCH_REGEX = /(?:[\s[{(<])([\w#b+^:-]*)$/;
|
||||
|
||||
function chordHandler(context) {
|
||||
// First check for chord context without quotes - block with empty completions
|
||||
let chordNoQuotesContext = context.matchBefore(CHORD_NO_QUOTES_REGEX);
|
||||
if (chordNoQuotesContext) {
|
||||
return {
|
||||
from: chordNoQuotesContext.to,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Then check for chord context with quotes - provide completions
|
||||
let chordContext = context.matchBefore(CHORD_WITH_QUOTES_REGEX);
|
||||
if (!chordContext) return null;
|
||||
|
||||
const text = chordContext.text;
|
||||
const quoteIdx = Math.max(text.lastIndexOf('"'), text.lastIndexOf("'"));
|
||||
if (quoteIdx === -1) return null;
|
||||
const inside = text.slice(quoteIdx + 1);
|
||||
|
||||
// Use same fragment matching as sound/mode for expressions like "<G Am>"
|
||||
const fragMatch = inside.match(CHORD_FRAGMENT_MATCH_REGEX);
|
||||
const fragment = fragMatch ? fragMatch[1] : inside;
|
||||
|
||||
// Check if fragment contains any pitch name at start (for root + symbol)
|
||||
let rootMatch = null;
|
||||
let symbolFragment = fragment;
|
||||
for (const pitch of pitchNames) {
|
||||
if (fragment.toLowerCase().startsWith(pitch.toLowerCase())) {
|
||||
rootMatch = pitch;
|
||||
symbolFragment = fragment.slice(pitch.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rootMatch) {
|
||||
// We have a root, now complete chord symbols
|
||||
const filteredSymbols = chordSymbolCompletions.filter((s) =>
|
||||
s.label.toLowerCase().startsWith(symbolFragment.toLowerCase()),
|
||||
);
|
||||
|
||||
// Create completions that replace the entire chord, not just the symbol part
|
||||
const options = filteredSymbols;
|
||||
|
||||
const from = chordContext.to - symbolFragment.length;
|
||||
return { from, options };
|
||||
} else {
|
||||
// No root yet, complete with pitch names
|
||||
const filteredPitches = pitchNames.filter((p) => p.toLowerCase().startsWith(fragment.toLowerCase()));
|
||||
const options = filteredPitches.map((p) => ({ label: p, type: 'pitch' }));
|
||||
const from = chordContext.to - fragment.length;
|
||||
return { from, options };
|
||||
}
|
||||
}
|
||||
|
||||
// Cached regex patterns for fallbackHandler
|
||||
const FALLBACK_WORD_REGEX = /\w*/;
|
||||
|
||||
function fallbackHandler(context) {
|
||||
const word = context.matchBefore(FALLBACK_WORD_REGEX);
|
||||
if (word && word.from === word.to && !context.explicit) return null;
|
||||
if (word) {
|
||||
return {
|
||||
from: word.from,
|
||||
options: jsdocCompletions,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlers = [
|
||||
soundHandler,
|
||||
bankHandler,
|
||||
chordHandler,
|
||||
scaleHandler,
|
||||
modeHandler,
|
||||
// this handler *must* be last
|
||||
fallbackHandler,
|
||||
];
|
||||
|
||||
export const strudelAutocomplete = (context) => {
|
||||
for (const handler of handlers) {
|
||||
const result = handler(context);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isAutoCompletionEnabled = (enabled) =>
|
||||
enabled ? [autocompletion({ override: [strudelAutocomplete], closeOnBlur: false })] : [];
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import {
|
||||
keymap,
|
||||
highlightSpecialChars,
|
||||
drawSelection,
|
||||
highlightActiveLine,
|
||||
dropCursor,
|
||||
rectangularSelection,
|
||||
crosshairCursor,
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter,
|
||||
} from '@codemirror/view';
|
||||
import {
|
||||
defaultHighlightStyle,
|
||||
syntaxHighlighting,
|
||||
bracketMatching,
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
} from '@codemirror/language';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { completionKeymap, closeBracketsKeymap } from '@codemirror/autocomplete';
|
||||
|
||||
// Taken + slightly modified from https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
|
||||
|
||||
export const basicSetup = (() => [
|
||||
// lineNumbers(),
|
||||
// highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
// foldGutter(),
|
||||
// drawSelection(),
|
||||
dropCursor(),
|
||||
// EditorState.allowMultipleSelections.of(true),
|
||||
// indentOnInput(),
|
||||
// syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
// autocompletion(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
// highlightActiveLine(),
|
||||
// highlightSelectionMatches(),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
// ...searchKeymap,
|
||||
...historyKeymap,
|
||||
// ...foldKeymap,
|
||||
// ...completionKeymap,
|
||||
]),
|
||||
])();
|
||||
|
||||
/// A minimal set of extensions to create a functional editor. Only
|
||||
/// includes [the default keymap](#commands.defaultKeymap), [undo
|
||||
/// history](#commands.history), [special character
|
||||
/// highlighting](#view.highlightSpecialChars), [custom selection
|
||||
/// drawing](#view.drawSelection), and [default highlight
|
||||
/// style](#language.defaultHighlightStyle).
|
||||
export const minimalSetup = (() => [
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||
])();
|
||||
|
|
@ -1,386 +0,0 @@
|
|||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { defaultHighlightStyle, syntaxHighlighting, bracketMatching } from '@codemirror/language';
|
||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||
import {
|
||||
EditorView,
|
||||
highlightActiveLineGutter,
|
||||
highlightActiveLine,
|
||||
keymap,
|
||||
lineNumbers,
|
||||
drawSelection,
|
||||
} from '@codemirror/view';
|
||||
import { repl, registerControl } from '@strudel/core';
|
||||
import { Drawer, cleanupDraw } from '@strudel/draw';
|
||||
import { isAutoCompletionEnabled } from './autocomplete.mjs';
|
||||
import { isTooltipEnabled } from './tooltip.mjs';
|
||||
import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
||||
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||
import { widgetPlugin, updateWidgets } from './widget.mjs';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
import { basicSetup } from './basicSetup.mjs';
|
||||
|
||||
const extensions = {
|
||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||
isBracketMatchingEnabled: (on) => (on ? bracketMatching({ brackets: '()[]{}<>' }) : []),
|
||||
isBracketClosingEnabled: (on) => (on ? closeBrackets() : []),
|
||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||
theme,
|
||||
isAutoCompletionEnabled,
|
||||
isTooltipEnabled,
|
||||
isPatternHighlightingEnabled,
|
||||
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
|
||||
isFlashEnabled,
|
||||
keybindings,
|
||||
isTabIndentationEnabled: (on) => (on ? keymap.of([indentWithTab]) : []),
|
||||
isMultiCursorEnabled: (on) =>
|
||||
on
|
||||
? [
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey),
|
||||
]
|
||||
: [],
|
||||
};
|
||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
export const defaultSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isBracketMatchingEnabled: false,
|
||||
isBracketClosingEnabled: true,
|
||||
isLineNumbersDisplayed: true,
|
||||
isActiveLineHighlighted: false,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
isTabIndentationEnabled: false,
|
||||
isMultiCursorEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 18,
|
||||
};
|
||||
|
||||
export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
|
||||
encode: JSON.stringify,
|
||||
decode: JSON.parse,
|
||||
});
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo }) {
|
||||
const settings = codemirrorSettings.get();
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
|
||||
initTheme(settings.theme);
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
extensions: [
|
||||
/* search(),
|
||||
highlightSelectionMatches(), */
|
||||
...initialSettings,
|
||||
basicSetup,
|
||||
mondo ? [] : javascript(),
|
||||
javascriptLanguage.data.of({
|
||||
closeBrackets: { brackets: ['(', '[', '{', "'", '"', '<'] },
|
||||
bracketMatching: { brackets: ['(', '[', '{', "'", '"', '<'] },
|
||||
}),
|
||||
sliderPlugin,
|
||||
widgetPlugin,
|
||||
// indentOnInput(), // works without. already brought with javascript extension?
|
||||
// bracketMatching(), // does not do anything
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
EditorView.updateListener.of((v) => onChange(v)),
|
||||
drawSelection({ cursorBlinkRate: 0 }),
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
preventDefault: true,
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-Enter',
|
||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||
}, */
|
||||
]),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
return new EditorView({
|
||||
state,
|
||||
parent: root,
|
||||
});
|
||||
}
|
||||
|
||||
export class StrudelMirror {
|
||||
constructor(options) {
|
||||
const {
|
||||
root,
|
||||
id,
|
||||
initialCode = '',
|
||||
onDraw,
|
||||
drawContext,
|
||||
drawTime = [0, 0],
|
||||
autodraw,
|
||||
prebake,
|
||||
bgFill = true,
|
||||
solo = true,
|
||||
...replOptions
|
||||
} = options;
|
||||
this.code = initialCode;
|
||||
this.root = root;
|
||||
this.miniLocations = [];
|
||||
this.widgets = [];
|
||||
this.drawTime = drawTime;
|
||||
this.drawContext = drawContext;
|
||||
this.onDraw = onDraw || this.draw;
|
||||
this.id = id || s4();
|
||||
this.solo = solo;
|
||||
|
||||
this.drawer = new Drawer((haps, time, _, painters) => {
|
||||
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||
this.highlight(currentFrame, time);
|
||||
this.onDraw(haps, time, painters);
|
||||
}, drawTime);
|
||||
|
||||
this.prebaked = prebake();
|
||||
autodraw && this.drawFirstFrame();
|
||||
this.repl = repl({
|
||||
...replOptions,
|
||||
id,
|
||||
onToggle: (started) => {
|
||||
replOptions?.onToggle?.(started);
|
||||
if (started) {
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
if (this.solo) {
|
||||
// stop other repls when this one is started
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('start-repl', {
|
||||
detail: this.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
cleanupDraw(true, id);
|
||||
}
|
||||
},
|
||||
beforeEval: async () => {
|
||||
cleanupDraw(true, id);
|
||||
await this.prebaked;
|
||||
await replOptions?.beforeEval?.();
|
||||
},
|
||||
afterEval: (options) => {
|
||||
// remember for when highlighting is toggled on
|
||||
this.miniLocations = options.meta?.miniLocations;
|
||||
this.widgets = options.meta?.widgets;
|
||||
const sliders = this.widgets.filter((w) => w.type === 'slider');
|
||||
updateSliderWidgets(this.editor, sliders);
|
||||
const widgets = this.widgets.filter((w) => w.type !== 'slider');
|
||||
updateWidgets(this.editor, widgets);
|
||||
updateMiniLocations(this.editor, this.miniLocations);
|
||||
replOptions?.afterEval?.(options);
|
||||
// if no painters are set (.onPaint was not called), then we only need the present moment (for highlighting)
|
||||
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
||||
this.drawer.setDrawTime(drawTime);
|
||||
// invalidate drawer after we've set the appropriate drawTime
|
||||
this.drawer.invalidate(this.repl.scheduler);
|
||||
},
|
||||
});
|
||||
this.editor = initEditor({
|
||||
root,
|
||||
initialCode,
|
||||
onChange: (v) => {
|
||||
if (v.docChanged) {
|
||||
this.code = v.state.doc.toString();
|
||||
this.repl.setCode?.(this.code);
|
||||
}
|
||||
},
|
||||
onEvaluate: () => this.evaluate(),
|
||||
onStop: () => this.stop(),
|
||||
mondo: replOptions.mondo,
|
||||
});
|
||||
const cmEditor = this.root.querySelector('.cm-editor');
|
||||
if (cmEditor) {
|
||||
this.root.style.display = 'block';
|
||||
if (bgFill) {
|
||||
this.root.style.backgroundColor = 'var(--background)';
|
||||
}
|
||||
cmEditor.style.backgroundColor = 'transparent';
|
||||
}
|
||||
const settings = codemirrorSettings.get();
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
|
||||
// stop this repl when another repl is started
|
||||
this.onStartRepl = (e) => {
|
||||
if (this.solo && e.detail !== this.id) {
|
||||
this.stop();
|
||||
}
|
||||
};
|
||||
document.addEventListener('start-repl', this.onStartRepl);
|
||||
}
|
||||
draw(haps, time, painters) {
|
||||
painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
|
||||
}
|
||||
async drawFirstFrame() {
|
||||
if (!this.onDraw) {
|
||||
return;
|
||||
}
|
||||
// draw first frame instantly
|
||||
await this.prebaked;
|
||||
try {
|
||||
await this.repl.evaluate(this.code, false);
|
||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.drawer.painters);
|
||||
} catch (err) {
|
||||
console.warn('first frame could not be painted');
|
||||
}
|
||||
}
|
||||
async evaluate() {
|
||||
this.flash();
|
||||
await this.repl.evaluate(this.code);
|
||||
}
|
||||
async stop() {
|
||||
this.repl.scheduler.stop();
|
||||
}
|
||||
async toggle() {
|
||||
if (this.repl.scheduler.started) {
|
||||
this.repl.stop();
|
||||
} else {
|
||||
this.evaluate();
|
||||
}
|
||||
}
|
||||
flash(ms) {
|
||||
flash(this.editor, ms);
|
||||
}
|
||||
highlight(haps, time) {
|
||||
highlightMiniLocations(this.editor, time, haps);
|
||||
}
|
||||
setFontSize(size) {
|
||||
this.root.style.fontSize = size + 'px';
|
||||
}
|
||||
setFontFamily(family) {
|
||||
this.root.style.fontFamily = family;
|
||||
const scroller = this.root.querySelector('.cm-scroller');
|
||||
if (scroller) {
|
||||
scroller.style.fontFamily = family;
|
||||
}
|
||||
}
|
||||
reconfigureExtension(key, value) {
|
||||
if (!extensions[key]) {
|
||||
console.warn(`extension ${key} is not known`);
|
||||
return;
|
||||
}
|
||||
value = parseBooleans(value);
|
||||
const newValue = extensions[key](value, this);
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
});
|
||||
if (key === 'theme') {
|
||||
activateTheme(value);
|
||||
}
|
||||
}
|
||||
setLineWrappingEnabled(enabled) {
|
||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||
}
|
||||
setBracketMatchingEnabled(enabled) {
|
||||
this.reconfigureExtension('isBracketMatchingEnabled', enabled);
|
||||
}
|
||||
setLineNumbersDisplayed(enabled) {
|
||||
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
||||
}
|
||||
setBracketClosingEnabled(enabled) {
|
||||
this.reconfigureExtension('isBracketClosingEnabled', enabled);
|
||||
}
|
||||
setTheme(theme) {
|
||||
this.reconfigureExtension('theme', theme);
|
||||
}
|
||||
setAutocompletionEnabled(enabled) {
|
||||
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
||||
}
|
||||
updateSettings(settings) {
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
for (let key in extensions) {
|
||||
this.reconfigureExtension(key, settings[key]);
|
||||
}
|
||||
const updated = { ...codemirrorSettings.get(), ...settings };
|
||||
codemirrorSettings.set(updated);
|
||||
}
|
||||
changeSetting(key, value) {
|
||||
if (extensions[key]) {
|
||||
this.reconfigureExtension(key, value);
|
||||
return;
|
||||
} else if (key === 'fontFamily') {
|
||||
this.setFontFamily(value);
|
||||
} else if (key === 'fontSize') {
|
||||
this.setFontSize(value);
|
||||
}
|
||||
}
|
||||
setCode(code) {
|
||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||
this.editor.dispatch({ changes });
|
||||
}
|
||||
clear() {
|
||||
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
|
||||
}
|
||||
getCursorLocation() {
|
||||
return this.editor.state.selection.main.head;
|
||||
}
|
||||
setCursorLocation(col) {
|
||||
return this.editor.dispatch({ selection: { anchor: col } });
|
||||
}
|
||||
appendCode(code) {
|
||||
const cursor = this.getCursorLocation();
|
||||
this.setCode(this.code + code);
|
||||
this.setCursorLocation(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
function parseBooleans(value) {
|
||||
return { true: true, false: false }[value] ?? value;
|
||||
}
|
||||
|
||||
// helper function to generate repl ids
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the css of highlighted events. Make sure to use single quotes!
|
||||
* @name markcss
|
||||
* @example
|
||||
* note("c a f e")
|
||||
* .markcss('text-decoration:underline')
|
||||
*/
|
||||
export const markcss = registerControl('markcss');
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
export const setFlash = StateEffect.define();
|
||||
export const flashField = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(flash, tr) {
|
||||
try {
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setFlash)) {
|
||||
if (e.value && tr.newDoc.length > 0) {
|
||||
const mark = Decoration.mark({
|
||||
attributes: { style: `background-color: rgba(255,255,255, .4); filter: invert(10%)` },
|
||||
});
|
||||
flash = Decoration.set([mark.range(0, tr.newDoc.length)]);
|
||||
} else {
|
||||
flash = Decoration.set([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flash;
|
||||
} catch (err) {
|
||||
console.warn('flash error', err);
|
||||
return flash;
|
||||
}
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
export const flash = (view, ms = 200) => {
|
||||
view.dispatch({ effects: setFlash.of(true) });
|
||||
setTimeout(() => {
|
||||
view.dispatch({ effects: setFlash.of(false) });
|
||||
}, ms);
|
||||
};
|
||||
|
||||
export const isFlashEnabled = (on) => (on ? flashField : []);
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
import { RangeSetBuilder, StateEffect, StateField, Prec } from '@codemirror/state';
|
||||
import { Decoration, EditorView } from '@codemirror/view';
|
||||
|
||||
export const setMiniLocations = StateEffect.define();
|
||||
export const showMiniLocations = StateEffect.define();
|
||||
export const updateMiniLocations = (view, locations) => {
|
||||
view.dispatch({ effects: setMiniLocations.of(locations) });
|
||||
};
|
||||
export const highlightMiniLocations = (view, atTime, haps) => {
|
||||
view.dispatch({ effects: showMiniLocations.of({ atTime, haps }) });
|
||||
};
|
||||
|
||||
const miniLocations = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(locations, tr) {
|
||||
if (tr.docChanged) {
|
||||
locations = locations.map(tr.changes);
|
||||
}
|
||||
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setMiniLocations)) {
|
||||
// this is called on eval, with the mini locations obtained from the transpiler
|
||||
// codemirror will automatically remap the marks when the document is edited
|
||||
// create a mark for each mini location, adding the range to the spec to find it later
|
||||
const marks = e.value
|
||||
.filter(([from]) => from < tr.newDoc.length)
|
||||
.map(([from, to]) => [from, Math.min(to, tr.newDoc.length)])
|
||||
.map(
|
||||
(range) =>
|
||||
Decoration.mark({
|
||||
id: range.join(':'),
|
||||
// this green is only to verify that the decoration moves when the document is edited
|
||||
// it will be removed later, so the mark is not visible by default
|
||||
attributes: { style: `background-color: #00CA2880` },
|
||||
}).range(...range), // -> Decoration
|
||||
);
|
||||
|
||||
locations = Decoration.set(marks, true); // -> DecorationSet === RangeSet<Decoration>
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
},
|
||||
});
|
||||
|
||||
const visibleMiniLocations = StateField.define({
|
||||
create() {
|
||||
return { atTime: 0, haps: new Map() };
|
||||
},
|
||||
update(visible, tr) {
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(showMiniLocations)) {
|
||||
// this is called every frame to show the locations that are currently active
|
||||
// we can NOT create new marks because the context.locations haven't changed since eval time
|
||||
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
|
||||
const haps = new Map();
|
||||
for (let hap of e.value.haps) {
|
||||
if (!hap.context?.locations || !hap.whole) {
|
||||
continue;
|
||||
}
|
||||
for (let { start, end } of hap.context.locations) {
|
||||
let id = `${start}:${end}`;
|
||||
if (!haps.has(id) || haps.get(id).whole.begin.lt(hap.whole.begin)) {
|
||||
haps.set(id, hap);
|
||||
}
|
||||
}
|
||||
}
|
||||
visible = { atTime: e.value.atTime, haps };
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
},
|
||||
});
|
||||
|
||||
// // Derive the set of decorations from the miniLocations and visibleLocations
|
||||
const miniLocationHighlights = EditorView.decorations.compute([miniLocations, visibleMiniLocations], (state) => {
|
||||
const iterator = state.field(miniLocations).iter();
|
||||
const { haps } = state.field(visibleMiniLocations);
|
||||
const builder = new RangeSetBuilder();
|
||||
|
||||
while (iterator.value) {
|
||||
const {
|
||||
from,
|
||||
to,
|
||||
value: {
|
||||
spec: { id },
|
||||
},
|
||||
} = iterator;
|
||||
|
||||
if (haps.has(id)) {
|
||||
const hap = haps.get(id);
|
||||
const color = hap.value?.color ?? 'var(--foreground)';
|
||||
const style = hap.value?.markcss || `outline: solid 2px ${color}`;
|
||||
// Get explicit channels for color values
|
||||
/*
|
||||
const swatch = document.createElement('div');
|
||||
swatch.style.color = color;
|
||||
document.body.appendChild(swatch);
|
||||
let channels = getComputedStyle(swatch)
|
||||
.color.match(/^rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})(?:,\s*(\d*(?:\.\d+)?))?\)$/)
|
||||
.slice(1)
|
||||
.map((c) => parseFloat(c || 1));
|
||||
document.body.removeChild(swatch);
|
||||
|
||||
// Get percentage of event
|
||||
const percent = 1 - (atTime - hap.whole.begin) / hap.whole.duration;
|
||||
channels[3] *= percent;
|
||||
*/
|
||||
|
||||
builder.add(
|
||||
from,
|
||||
to,
|
||||
Decoration.mark({
|
||||
// attributes: { style: `outline: solid 2px rgba(${channels.join(', ')})` },
|
||||
attributes: { style },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
});
|
||||
|
||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||
|
||||
export const isPatternHighlightingEnabled = (on, config) => {
|
||||
on &&
|
||||
config &&
|
||||
setTimeout(() => {
|
||||
updateMiniLocations(config.editor, config.miniLocations);
|
||||
}, 100);
|
||||
return on ? Prec.highest(highlightExtension) : [];
|
||||
};
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
export let html = (string) => {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = string.trim();
|
||||
return template.content.childNodes;
|
||||
};
|
||||
let parseChunk = (chunk) => {
|
||||
if (Array.isArray(chunk)) return chunk.flat().join('');
|
||||
if (chunk === undefined) return '';
|
||||
return chunk;
|
||||
};
|
||||
export let h = (strings, ...vars) => {
|
||||
let string = '';
|
||||
for (let i in strings) {
|
||||
string += parseChunk(strings[i]);
|
||||
string += parseChunk(vars[i]);
|
||||
}
|
||||
return html(string);
|
||||
};
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export * from './codemirror.mjs';
|
||||
export * from './highlight.mjs';
|
||||
export * from './flash.mjs';
|
||||
export * from './slider.mjs';
|
||||
export * from './themes.mjs';
|
||||
export * from './widget.mjs';
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { Prec } from '@codemirror/state';
|
||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||
// import { searchKeymap } from '@codemirror/search';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
// import { vim } from './vim_test.mjs';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
|
||||
const vscodePlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor() {}
|
||||
},
|
||||
{
|
||||
provide: () => {
|
||||
return Prec.highest(keymap.of([...vscodeKeymap]));
|
||||
},
|
||||
},
|
||||
);
|
||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||
|
||||
const keymaps = {
|
||||
vim,
|
||||
emacs,
|
||||
codemirror: () => keymap.of(defaultKeymap),
|
||||
vscode: vscodeExtension,
|
||||
};
|
||||
|
||||
export function keybindings(name) {
|
||||
const active = keymaps[name];
|
||||
return [active ? Prec.high(active()) : []];
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"name": "@strudel/codemirror",
|
||||
"version": "1.2.5",
|
||||
"description": "Codemirror Extensions for Strudel",
|
||||
"main": "index.mjs",
|
||||
"publishConfig": {
|
||||
"main": "dist/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://codeberg.org/uzu/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"contributors": [
|
||||
"Alex McLean <alex@slab.org>"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://codeberg.org/uzu/strudel/issues"
|
||||
},
|
||||
"homepage": "https://codeberg.org/uzu/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.4",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/language": "^6.10.8",
|
||||
"@codemirror/search": "^6.5.8",
|
||||
"@codemirror/state": "^6.5.1",
|
||||
"@codemirror/view": "^6.36.2",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@replit/codemirror-emacs": "^6.1.0",
|
||||
"@replit/codemirror-vim": "^6.3.0",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@strudel/core": "workspace:*",
|
||||
"@strudel/draw": "workspace:*",
|
||||
"@strudel/tonal": "workspace:*",
|
||||
"@strudel/transpiler": "workspace:*",
|
||||
"@tonaljs/tonal": "^4.10.0",
|
||||
"superdough": "workspace:*",
|
||||
"nanostores": "^0.11.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
import { ref, pure } from '@strudel/core';
|
||||
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { StateEffect } from '@codemirror/state';
|
||||
|
||||
export let sliderValues = {};
|
||||
const getSliderID = (from) => `slider_${from}`;
|
||||
|
||||
export class SliderWidget extends WidgetType {
|
||||
constructor(value, min, max, from, to, step, view) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.from = from;
|
||||
this.originalFrom = from;
|
||||
this.to = to;
|
||||
this.step = step;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
eq() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let wrap = document.createElement('span');
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.className = 'cm-slider'; // inline-flex items-center
|
||||
let slider = wrap.appendChild(document.createElement('input'));
|
||||
slider.type = 'range';
|
||||
slider.min = this.min;
|
||||
slider.max = this.max;
|
||||
slider.step = this.step ?? (this.max - this.min) / 1000;
|
||||
slider.originalValue = this.value;
|
||||
// to make sure the code stays in sync, let's save the original value
|
||||
// becuase .value automatically clamps values so it'll desync with the code
|
||||
slider.value = slider.originalValue;
|
||||
slider.from = this.from;
|
||||
slider.originalFrom = this.originalFrom;
|
||||
slider.to = this.to;
|
||||
slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)';
|
||||
this.slider = slider;
|
||||
slider.addEventListener('input', (e) => {
|
||||
const next = e.target.value;
|
||||
let insert = next;
|
||||
//let insert = next.toFixed(2);
|
||||
const to = slider.from + slider.originalValue.length;
|
||||
let change = { from: slider.from, to, insert };
|
||||
slider.originalValue = insert;
|
||||
slider.value = insert;
|
||||
this.view.dispatch({ changes: change });
|
||||
const id = getSliderID(slider.originalFrom); // matches id generated in transpiler
|
||||
window.postMessage({ type: 'cm-slider', value: Number(next), id });
|
||||
});
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ignoreEvent(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const setSliderWidgets = StateEffect.define();
|
||||
|
||||
export const updateSliderWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: setSliderWidgets.of(widgets) });
|
||||
};
|
||||
|
||||
function getSliders(widgetConfigs, view) {
|
||||
return widgetConfigs
|
||||
.filter((w) => w.type === 'slider')
|
||||
.map(({ from, to, value, min, max, step }) => {
|
||||
return Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, from, to, step, view),
|
||||
side: 0,
|
||||
}).range(from /* , to */);
|
||||
});
|
||||
}
|
||||
|
||||
export const sliderPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
|
||||
constructor(view /* : EditorView */) {
|
||||
this.decorations = Decoration.set([]);
|
||||
}
|
||||
|
||||
update(update /* : ViewUpdate */) {
|
||||
update.transactions.forEach((tr) => {
|
||||
if (tr.docChanged) {
|
||||
this.decorations = this.decorations.map(tr.changes);
|
||||
const iterator = this.decorations.iter();
|
||||
while (iterator.value) {
|
||||
// when the widgets are moved, we need to tell the dom node the current position
|
||||
// this is important because the updateSliderValue function has to work with the dom node
|
||||
if (iterator.value?.widget?.slider) {
|
||||
iterator.value.widget.slider.from = iterator.from;
|
||||
iterator.value.widget.slider.to = iterator.to;
|
||||
}
|
||||
iterator.next();
|
||||
}
|
||||
}
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setSliderWidgets)) {
|
||||
this.decorations = Decoration.set(getSliders(e.value, update.view));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays a slider widget to allow the user manipulate a value
|
||||
*
|
||||
* @name slider
|
||||
* @param {number} value Initial value
|
||||
* @param {number} min Minimum value - optional, defaults to 0
|
||||
* @param {number} max Maximum value - optional, defaults to 1
|
||||
* @param {number} step Step size - optional
|
||||
*/
|
||||
export let slider = (value) => {
|
||||
console.warn('slider will only work when the transpiler is used... passing value as is');
|
||||
return pure(value);
|
||||
};
|
||||
// function transpiled from slider = (value, min, max)
|
||||
export let sliderWithID = (id, value, min, max) => {
|
||||
sliderValues[id] = value; // sync state at eval time (code -> state)
|
||||
return ref(() => sliderValues[id]); // use state at query time
|
||||
};
|
||||
// update state when sliders are moved
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.data.type === 'cm-slider') {
|
||||
if (sliderValues[e.data.id] !== undefined) {
|
||||
// update state when slider is moved
|
||||
sliderValues[e.data.id] = e.data.value;
|
||||
} else {
|
||||
console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
218
src/strudel/codemirror/themes.mjs
vendored
218
src/strudel/codemirror/themes.mjs
vendored
|
|
@ -1,218 +0,0 @@
|
|||
import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
|
||||
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
|
||||
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
|
||||
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
|
||||
import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
|
||||
import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
|
||||
import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
|
||||
import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
|
||||
import redText, { settings as redTextSettings } from './themes/red-text.mjs';
|
||||
import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
|
||||
import archBtw, { settings as archBtwSettings } from './themes/archBtw.mjs';
|
||||
import fruitDaw, { settings as fruitDawSettings } from './themes/fruitDaw.mjs';
|
||||
|
||||
import bluescreenlight, { settings as bluescreenlightsettings } from './themes/bluescreenlight.mjs';
|
||||
|
||||
import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
|
||||
import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
|
||||
import aura, { settings as auraSettings } from './themes/aura.mjs';
|
||||
import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
|
||||
import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
|
||||
import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
|
||||
import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
|
||||
import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
|
||||
import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
|
||||
import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
|
||||
import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
|
||||
import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
|
||||
import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
|
||||
import nord, { settings as nordSettings } from './themes/nord.mjs';
|
||||
import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
|
||||
import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
|
||||
import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
|
||||
import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
|
||||
import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
|
||||
import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs';
|
||||
import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs';
|
||||
import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs';
|
||||
import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs';
|
||||
import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs';
|
||||
import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs';
|
||||
import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs';
|
||||
|
||||
import { setTheme } from '@strudel/draw';
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
algoboy,
|
||||
archBtw,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bbedit,
|
||||
blackscreen,
|
||||
bluescreen,
|
||||
bluescreenlight,
|
||||
CutiePi,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
fruitDaw,
|
||||
githubDark,
|
||||
githubLight,
|
||||
greenText,
|
||||
gruvboxDark,
|
||||
gruvboxLight,
|
||||
sonicPink,
|
||||
materialDark,
|
||||
materialLight,
|
||||
monokai,
|
||||
noctisLilac,
|
||||
nord,
|
||||
redText,
|
||||
solarizedDark,
|
||||
solarizedLight,
|
||||
sublime,
|
||||
teletext,
|
||||
tokyoNight,
|
||||
tokyoNightDay,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
vscodeLight,
|
||||
whitescreen,
|
||||
xcodeLight,
|
||||
};
|
||||
|
||||
export const settings = {
|
||||
strudelTheme: strudelThemeSettings,
|
||||
bluescreen: bluescreenSettings,
|
||||
bluescreenlight: bluescreenlightsettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
teletext: teletextSettings,
|
||||
algoboy: algoboySettings,
|
||||
archBtw: archBtwSettings,
|
||||
androidstudio: androidstudioSettings,
|
||||
atomone: atomOneSettings,
|
||||
aura: auraSettings,
|
||||
bbedit: bbeditSettings,
|
||||
darcula: darculaSettings,
|
||||
dracula: draculaSettings,
|
||||
duotoneDark: duotoneDarkSettings,
|
||||
eclipse: eclipseSettings,
|
||||
CutiePi: CutiePiSettings,
|
||||
sonicPink: sonicPinkSettings,
|
||||
fruitDaw: fruitDawSettings,
|
||||
githubLight: githubLightSettings,
|
||||
githubDark: githubDarkSettings,
|
||||
greenText: greenTextSettings,
|
||||
|
||||
gruvboxDark: gruvboxDarkSettings,
|
||||
gruvboxLight: gruvboxLightSettings,
|
||||
materialDark: materialDarkSettings,
|
||||
materialLight: materialLightSettings,
|
||||
noctisLilac: noctisLilacSettings,
|
||||
nord: nordSettings,
|
||||
monokai: monokaiSettings,
|
||||
redText: redTextSettings,
|
||||
solarizedLight: solarizedLightSettings,
|
||||
solarizedDark: solarizedDarkSettings,
|
||||
sublime: sublimeSettings,
|
||||
tokyoNight: tokyoNightSettings,
|
||||
tokyoNightStorm: tokyoNightStormSettings,
|
||||
vscodeDark: vscodeDarkSettings,
|
||||
vscodeLight: vscodeLightSettings,
|
||||
xcodeLight: xcodeLightSettings,
|
||||
tokyoNightDay: tokyoNightDaySettings,
|
||||
};
|
||||
|
||||
function getColors(str) {
|
||||
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
|
||||
const colors = [];
|
||||
|
||||
let match;
|
||||
while ((match = colorRegex.exec(str)) !== null) {
|
||||
const color = match[0];
|
||||
if (!colors.includes(color)) {
|
||||
colors.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
export function themeColors(theme) {
|
||||
return getColors(stringifySafe(theme));
|
||||
}
|
||||
|
||||
function getCircularReplacer() {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function stringifySafe(json) {
|
||||
return JSON.stringify(json, getCircularReplacer());
|
||||
}
|
||||
|
||||
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||
|
||||
// css style injection helpers
|
||||
export function injectStyle(rule) {
|
||||
const newStyle = document.createElement('style');
|
||||
document.head.appendChild(newStyle);
|
||||
const styleSheet = newStyle.sheet;
|
||||
const ruleIndex = styleSheet.insertRule(rule, 0);
|
||||
return () => styleSheet.deleteRule(ruleIndex);
|
||||
}
|
||||
|
||||
let currentTheme,
|
||||
resetThemeStyle,
|
||||
themeStyle,
|
||||
styleID = 'strudel-theme-vars';
|
||||
export function initTheme(theme) {
|
||||
if (!document.getElementById(styleID)) {
|
||||
themeStyle = document.createElement('style');
|
||||
themeStyle.id = styleID;
|
||||
document.head.append(themeStyle);
|
||||
}
|
||||
activateTheme(theme);
|
||||
}
|
||||
|
||||
export function activateTheme(name) {
|
||||
if (currentTheme === name) {
|
||||
return;
|
||||
}
|
||||
currentTheme = name;
|
||||
if (!settings[name]) {
|
||||
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||
}
|
||||
const themeSettings = settings[name] || settings.strudelTheme;
|
||||
// set css variables
|
||||
themeStyle.innerHTML = `:root {
|
||||
${Object.entries(themeSettings)
|
||||
// important to override fallback
|
||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||
.join('\n')}
|
||||
}`;
|
||||
setTheme(themeSettings);
|
||||
// tailwind dark mode
|
||||
if (themeSettings.light) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
resetThemeStyle?.();
|
||||
resetThemeStyle = undefined;
|
||||
if (themeSettings.customStyle) {
|
||||
resetThemeStyle = injectStyle(themeSettings.customStyle);
|
||||
}
|
||||
}
|
||||
45
src/strudel/codemirror/themes/CutiePi.mjs
vendored
45
src/strudel/codemirror/themes/CutiePi.mjs
vendored
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Cutie Pi
|
||||
* by Switch Angel
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
const deepPurple = '#5c019a';
|
||||
const yellowPink = '#fbeffc';
|
||||
const grey = '#272C35';
|
||||
const pinkAccent = '#fee1ff';
|
||||
const lightGrey = '#465063';
|
||||
const bratGreen = '#9acd3f';
|
||||
const lighterGrey = '#97a1b7';
|
||||
const pink = '#f6a6fd';
|
||||
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
lineBackground: 'transparent',
|
||||
foreground: deepPurple,
|
||||
caret: '#797977',
|
||||
selection: yellowPink,
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: grey,
|
||||
gutterForeground: lightGrey,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: pinkAccent,
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: deepPurple,
|
||||
},
|
||||
{ tag: [t.tagName, t.heading], color: settings.foreground },
|
||||
{ tag: t.comment, color: lighterGrey },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: pink },
|
||||
{ tag: [t.attributeName, t.number], color: '#d19a66' },
|
||||
{ tag: t.className, color: grey },
|
||||
{ tag: t.keyword, color: deepPurple },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: bratGreen },
|
||||
],
|
||||
});
|
||||
61
src/strudel/codemirror/themes/algoboy.mjs
vendored
61
src/strudel/codemirror/themes/algoboy.mjs
vendored
|
|
@ -1,61 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const palettes = {
|
||||
// https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-09-920495662
|
||||
'Central Florida A': ['#FFF630', '#B3AC22', '#666213', '#191905'],
|
||||
'Central Florida B': ['#38CEBA', '#279082', '#16524A', '#061513'],
|
||||
'Central Florida C': ['#FF8836', '#B35F26', '#663616', '#190E05'],
|
||||
'Central Florida D': ['#E07070', '#9D4E4E', '#5A2D2D', '#160B0B'],
|
||||
'Central Florida E': ['#7AA4CB', '#55738E', '#314251', '#0C1014'],
|
||||
'Feminine Energy A': ['#DC5686', '#9A415E', '#582536', '#16090D'],
|
||||
'Feminine Energy B': ['#D0463C', '#92312A', '#531c18', '#150706'],
|
||||
'Feminine Energy C': ['#D86918', '#974A11', '#562A0A', '#160A02'],
|
||||
'Feminine Energy D': ['#EFC54F', '#A78A36', '#604F20', '#181408'],
|
||||
'Feminine Energy E': ['#866399', '#5e456b', '#36283d', '#0d0a0f'],
|
||||
'Sour Watermelon A': ['#993366', '#6B2447', '#3D1429', '#0F050A'],
|
||||
'Sour Watermelon B': ['#996666', '#6B4747', '#3D2929', '#0F0A0A'],
|
||||
'Sour Watermelon C': ['#999966', '#686B47', '#3d3d29', '#0f0f0A'],
|
||||
'Sour Watermelon D': ['#99cc66', '#6b8f47', '#3d5229', '#0f140a'],
|
||||
'Sour Watermelon E': ['#99ff66', '#6bb347', '#3d6629', '#0f190a'],
|
||||
//https://www.deviantart.com/advancedfan2020/art/Game-Boy-Palette-Set-Color-HEX-Part-02-920073260
|
||||
'Peri Peaceful A': ['#909BE9', '#656DA3', '#3A3E5D', '#0e0f17'],
|
||||
'Peri Peaceful B': ['#68628d', '#494563', '#2a2738', '#0a0a0e'], // pretty dim
|
||||
'Peri Peaceful E': ['#b5a0a9', '#7f7076', '#484044', '#121011'],
|
||||
'Hichem Palette B': ['#4fa3a5', '#377273', '#204142', '#081010'],
|
||||
'Hichem Palette C': ['#Fe6f9b', '#b24e6d', '#662c3e', '#190b0f'],
|
||||
'Hichem Palette D': ['#ffbb5a', '#b3833f', '#664b24', '#191309'],
|
||||
'JSR2 A': ['#E0EFC0', '#9da786', '#5a604d', '#161813'],
|
||||
};
|
||||
const palette = palettes['Sour Watermelon B'];
|
||||
export const settings = {
|
||||
background: palette[3],
|
||||
foreground: palette[1],
|
||||
caret: palette[0],
|
||||
selection: palette[0],
|
||||
selectionMatch: palette[1],
|
||||
lineHighlight: palette[3],
|
||||
lineBackground: palette[3] + '90',
|
||||
//lineBackground: 'transparent',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: palette[0],
|
||||
light: false,
|
||||
// customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.comment, color: palette[2] },
|
||||
{ tag: t.string, color: palette[1] },
|
||||
{ tag: [t.atom, t.number], color: palette[1] },
|
||||
{ tag: [t.meta, t.labelName, t.variableName], color: palette[0] },
|
||||
{
|
||||
tag: [t.keyword, t.tagName, t.arithmeticOperator],
|
||||
color: palette[1],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: palette[0] },
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: palette[0] },
|
||||
{ tag: t.atom, color: palette[1] },
|
||||
],
|
||||
});
|
||||
43
src/strudel/codemirror/themes/androidstudio.mjs
vendored
43
src/strudel/codemirror/themes/androidstudio.mjs
vendored
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* androidstudio
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282b2e',
|
||||
lineBackground: '#282b2e99',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#343739',
|
||||
selectionMatch: '#343739',
|
||||
lineHighlight: '#343739',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282b2e',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#4e5254',
|
||||
selectionMatch: '#4e5254',
|
||||
gutterForeground: '#cccccc50',
|
||||
lineHighlight: '#7f85891f',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: [t.keyword, t.deleted, t.className], color: '#a9b7c6' },
|
||||
{ tag: [t.number, t.literal], color: '#6897bb' },
|
||||
//{ tag: [t.link, t.variableName], color: '#629755' },
|
||||
{ tag: [t.link, t.variableName], color: '#a9b7c6' },
|
||||
{ tag: [t.comment, t.quote], color: 'grey' },
|
||||
{ tag: [t.meta, t.documentMeta], color: '#bbb529' },
|
||||
//{ tag: [t.string, t.propertyName, t.attributeValue], color: '#6a8759' },
|
||||
{ tag: [t.propertyName, t.attributeValue], color: '#a9b7c6' },
|
||||
{ tag: [t.string], color: '#6a8759' },
|
||||
{ tag: [t.heading, t.typeName], color: '#ffc66d' },
|
||||
{ tag: [t.attributeName], color: '#a9b7c6' },
|
||||
{ tag: [t.emphasis], fontStyle: 'italic' },
|
||||
],
|
||||
});
|
||||
38
src/strudel/codemirror/themes/archBtw.mjs
vendored
38
src/strudel/codemirror/themes/archBtw.mjs
vendored
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Arch Btw
|
||||
* Modern terminal inspired theme
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['rgb(0, 0, 0)', 'rgb(82, 208, 250)', 'rgba(113, 208, 250, .4)', 'rgba(113, 208, 250, .15)'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[1],
|
||||
selection: hex[2],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[1],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[1] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[1] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
51
src/strudel/codemirror/themes/atomone.mjs
vendored
51
src/strudel/codemirror/themes/atomone.mjs
vendored
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#272C35',
|
||||
lineBackground: '#272C3599',
|
||||
foreground: 'hsl(220, 14%, 71%)',
|
||||
caret: '#797977',
|
||||
selection: '#ffffff30',
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2B323D',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#272C35',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#3d4c64',
|
||||
selectionMatch: '#3d4c64',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2e3f5940',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: 'hsl(207, 82%, 66%)',
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: 'hsl( 29, 54%, 61%)' },
|
||||
{ tag: [t.tagName, t.heading], color: '#e06c75' },
|
||||
{ tag: t.comment, color: '#54636D' },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: 'hsl(220, 14%, 71%)' },
|
||||
{ tag: [t.attributeName, t.number], color: 'hsl( 29, 54%, 61%)' },
|
||||
{ tag: t.className, color: 'hsl( 39, 67%, 69%)' },
|
||||
{ tag: t.keyword, color: 'hsl(286, 60%, 67%)' },
|
||||
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: '#98c379' },
|
||||
],
|
||||
});
|
||||
51
src/strudel/codemirror/themes/aura.mjs
vendored
51
src/strudel/codemirror/themes/aura.mjs
vendored
|
|
@ -1,51 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#21202e',
|
||||
lineBackground: '#21202e99',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#3d375e7f',
|
||||
selectionMatch: '#3d375e7f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#21202e',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#5a51898f',
|
||||
selectionMatch: '#5a51898f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#a277ff' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#edecee' },
|
||||
{ tag: [t.propertyName], color: '#ffca85' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#61ffca' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#ffca85' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#61ffca' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#edecee' },
|
||||
{ tag: [t.className], color: '#82e2ff' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#61ffca' },
|
||||
{ tag: [t.typeName], color: '#82e2ff' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#a277ff' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#61ffca' },
|
||||
{ tag: [t.meta, t.comment], color: '#6d6d6d' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#a277ff' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#edecee' },
|
||||
{ tag: t.invalid, color: '#ff6767' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
46
src/strudel/codemirror/themes/bbedit.mjs
vendored
46
src/strudel/codemirror/themes/bbedit.mjs
vendored
|
|
@ -1,46 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#FFFFFF',
|
||||
lineBackground: '#FFFFFF99',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#FFFFFF',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.meta, t.comment], color: '#804000' },
|
||||
{ tag: [t.keyword, t.strong], color: '#0000FF' },
|
||||
{ tag: [t.number], color: '#FF0080' },
|
||||
{ tag: [t.string], color: '#FF0080' },
|
||||
{ tag: [t.variableName], color: '#006600' },
|
||||
{ tag: [t.escape], color: '#33CC33' },
|
||||
{ tag: [t.tagName], color: '#1C02FF' },
|
||||
{ tag: [t.heading], color: '#0C07FF' },
|
||||
{ tag: [t.quote], color: '#000000' },
|
||||
{ tag: [t.list], color: '#B90690' },
|
||||
{ tag: [t.documentMeta], color: '#888888' },
|
||||
{ tag: [t.function(t.variableName)], color: '#0000A2' },
|
||||
{ tag: [t.definition(t.typeName), t.typeName], color: '#6D79DE' },
|
||||
],
|
||||
});
|
||||
39
src/strudel/codemirror/themes/blackscreen.mjs
vendored
39
src/strudel/codemirror/themes/blackscreen.mjs
vendored
|
|
@ -1,39 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
lineBackground: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'inherit' },
|
||||
],
|
||||
});
|
||||
42
src/strudel/codemirror/themes/bluescreen.mjs
vendored
42
src/strudel/codemirror/themes/bluescreen.mjs
vendored
|
|
@ -1,42 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'inherit' },
|
||||
],
|
||||
});
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* A lighter blue screen theme
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['rgb(75, 130, 247)', 'rgb(47, 108, 246)', 'rgb(255, 255, 255)', 'rgba(255, 255, 255,.3)'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[3],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[1],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[2] },
|
||||
{ tag: [t.comment, t.bracket, t.brace, t.compareOperator], color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[2] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
|
||||
],
|
||||
});
|
||||
47
src/strudel/codemirror/themes/darcula.mjs
vendored
47
src/strudel/codemirror/themes/darcula.mjs
vendored
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* darcula
|
||||
* Name: IntelliJ IDEA darcula theme
|
||||
* From IntelliJ IDEA by JetBrains
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: '#242424',
|
||||
lineBackground: '#24242499',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#242424',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#CCCCCC' },
|
||||
{ tag: [t.atom, t.number], color: '#7A9EC2' },
|
||||
{ tag: [t.comment], color: '#707070' },
|
||||
{ tag: [t.string], color: '#6A8759' },
|
||||
{ tag: [t.variableName, t.operator], color: '#CCCCCC' },
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: '#FFC66D' },
|
||||
{ tag: [t.meta, t.className], color: '#FFC66D' },
|
||||
{ tag: [t.propertyName], color: '#FFC66D' },
|
||||
{ tag: [t.keyword], color: '#CC7832' },
|
||||
{ tag: [t.tagName], color: '#ff79c6' },
|
||||
{ tag: [t.typeName], color: '#ffb86c' },
|
||||
],
|
||||
});
|
||||
50
src/strudel/codemirror/themes/dracula.mjs
vendored
50
src/strudel/codemirror/themes/dracula.mjs
vendored
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* @name dracula
|
||||
* Michael Kaminsky (http://github.com/mkaminsky11)
|
||||
* Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)
|
||||
*/
|
||||
// this is different from https://thememirror.net/dracula
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282a36',
|
||||
lineBackground: '#282a3699',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6272a4',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
};
|
||||
|
||||
const purple = '#BD93F9';
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282a36',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6272a4',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.comment, color: '#6272a4' },
|
||||
{ tag: t.string, color: '#f1fa8c' },
|
||||
{ tag: [t.atom, t.number], color: purple },
|
||||
{ tag: [t.meta, t.labelName, t.variableName], color: '#f8f8f2' },
|
||||
{
|
||||
tag: [t.keyword, t.tagName, t.arithmeticOperator],
|
||||
color: '#ff79c6',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.propertyName], color: '#50fa7b' },
|
||||
{ tag: t.atom, color: '#bd93f9' },
|
||||
],
|
||||
});
|
||||
42
src/strudel/codemirror/themes/duotoneDark.mjs
vendored
42
src/strudel/codemirror/themes/duotoneDark.mjs
vendored
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#2a2734',
|
||||
lineBackground: '#2a273499',
|
||||
foreground: '#eeebff',
|
||||
caret: '#ffad5c',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#2a2734',
|
||||
foreground: '#6c6783',
|
||||
caret: '#ffad5c',
|
||||
selection: '#9a86fd',
|
||||
selectionMatch: '#9a86fd',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.bracket, t.operator], color: '#6c6783' },
|
||||
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#ffcc99' },
|
||||
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.className, t.variableName], color: '#eeebff' },
|
||||
{ tag: [t.typeName, t.url], color: '#eeebff' },
|
||||
{ tag: t.string, color: '#ffb870' },
|
||||
/* { tag: [t.propertyName], color: '#9a86fd' }, */
|
||||
{ tag: [t.propertyName], color: '#eeebff' },
|
||||
{ tag: t.labelName, color: '#eeebff' },
|
||||
],
|
||||
});
|
||||
45
src/strudel/codemirror/themes/duotoneLight.mjs
vendored
45
src/strudel/codemirror/themes/duotoneLight.mjs
vendored
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* duotone
|
||||
* author Bram de Haan
|
||||
* by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes)
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#faf8f5',
|
||||
lineBackground: '#faf8f599',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#EFEFEF',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#faf8f5',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#ddceb154',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.bracket], color: '#b6ad9a' },
|
||||
{ tag: [t.atom, t.number, t.keyword, t.link, t.attributeName, t.quote], color: '#063289' },
|
||||
{ tag: [t.emphasis, t.heading, t.tagName, t.propertyName, t.variableName], color: '#2d2006' },
|
||||
{ tag: [t.typeName, t.url, t.string], color: '#896724' },
|
||||
{ tag: [t.operator, t.string], color: '#1659df' },
|
||||
{ tag: [t.propertyName], color: '#b29762' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#063289' },
|
||||
],
|
||||
});
|
||||
46
src/strudel/codemirror/themes/eclipse.mjs
vendored
46
src/strudel/codemirror/themes/eclipse.mjs
vendored
|
|
@ -1,46 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#e8f2ff',
|
||||
gutterBorder: 'transparent',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#006fff1c',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment], color: '#3F7F5F' },
|
||||
{ tag: [t.documentMeta], color: '#FF1717' },
|
||||
{ tag: t.keyword, color: '#7F0055', fontWeight: 'bold' },
|
||||
{ tag: t.atom, color: '#00f' },
|
||||
{ tag: t.number, color: '#164' },
|
||||
{ tag: t.propertyName, color: '#164' },
|
||||
{ tag: [t.variableName, t.definition(t.variableName)], color: '#0000C0' },
|
||||
{ tag: t.function(t.variableName), color: '#0000C0' },
|
||||
{ tag: t.string, color: '#2A00FF' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.tagName, color: '#170' },
|
||||
{ tag: t.attributeName, color: '#00c' },
|
||||
{ tag: t.link, color: '#219' },
|
||||
],
|
||||
});
|
||||
50
src/strudel/codemirror/themes/fruitDaw.mjs
vendored
50
src/strudel/codemirror/themes/fruitDaw.mjs
vendored
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Fruit Daw
|
||||
* made by Jade
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = [
|
||||
'rgb(84, 93, 98)',
|
||||
'rgb(255, 255, 255)',
|
||||
'rgba(255, 255, 255, .25)',
|
||||
'rgb(67, 76, 81)',
|
||||
'rgb(186, 230, 115)',
|
||||
'rgb(252, 184, 67)',
|
||||
'rgb(124, 206, 254)',
|
||||
'rgb(83, 101, 102)',
|
||||
'rgba(46, 62, 72,.5)',
|
||||
'rgb(94, 100, 108)',
|
||||
'rgb(167, 216, 177)',
|
||||
];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[10],
|
||||
selection: hex[8],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[3],
|
||||
gutterForeground: hex[2],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[3],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[1],
|
||||
},
|
||||
{ tag: [t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: [t.comment, t.brace, t.bracket], color: hex[2] },
|
||||
{ tag: [t.variableName], color: hex[1] },
|
||||
{ tag: [t.labelName, t.propertyName, t.self, t.atom], color: hex[5] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[6] },
|
||||
{ tag: t.keyword, color: hex[5] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[4] },
|
||||
],
|
||||
});
|
||||
42
src/strudel/codemirror/themes/githubDark.mjs
vendored
42
src/strudel/codemirror/themes/githubDark.mjs
vendored
|
|
@ -1,42 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#0d1117',
|
||||
lineBackground: '#0d111799',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#d2a8ff' },
|
||||
{ tag: [t.standard(t.tagName), t.tagName], color: '#7ee787' },
|
||||
{ tag: [t.comment, t.bracket], color: '#8b949e' },
|
||||
{ tag: [t.className, t.propertyName], color: '#d2a8ff' },
|
||||
{ tag: [t.variableName, t.attributeName], color: '#d2a8ff' },
|
||||
{ tag: [t.number, t.operator], color: '#79c0ff' },
|
||||
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#ff7b72' },
|
||||
{ tag: [t.string, t.meta, t.regexp], color: '#a5d6ff' },
|
||||
{ tag: [t.name, t.quote], color: '#7ee787' },
|
||||
{ tag: [t.heading, t.strong], color: '#d2a8ff', fontWeight: 'bold' },
|
||||
{ tag: [t.emphasis], color: '#d2a8ff', fontStyle: 'italic' },
|
||||
{ tag: [t.deleted], color: '#ffdcd7', backgroundColor: 'ffeef0' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#ffab70' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.invalid, color: '#f97583' },
|
||||
],
|
||||
});
|
||||
42
src/strudel/codemirror/themes/githubLight.mjs
vendored
42
src/strudel/codemirror/themes/githubLight.mjs
vendored
|
|
@ -1,42 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.standard(t.tagName), t.tagName], color: '#116329' },
|
||||
{ tag: [t.comment, t.bracket], color: '#6a737d' },
|
||||
{ tag: [t.className, t.propertyName], color: '#6f42c1' },
|
||||
{ tag: [t.variableName, t.attributeName, t.number, t.operator], color: '#005cc5' },
|
||||
{ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName], color: '#d73a49' },
|
||||
{ tag: [t.string, t.meta, t.regexp], color: '#032f62' },
|
||||
{ tag: [t.name, t.quote], color: '#22863a' },
|
||||
{ tag: [t.heading, t.strong], color: '#24292e', fontWeight: 'bold' },
|
||||
{ tag: [t.emphasis], color: '#24292e', fontStyle: 'italic' },
|
||||
{ tag: [t.deleted], color: '#b31d28', backgroundColor: 'ffeef0' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#e36209' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#032f62' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.invalid, color: '#cb2431' },
|
||||
],
|
||||
});
|
||||
39
src/strudel/codemirror/themes/green-text.mjs
vendored
39
src/strudel/codemirror/themes/green-text.mjs
vendored
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#000000', '#8ed675', '#56bd2a', '#54636D', '#171717'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[4],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[3],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: t.comment, color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
82
src/strudel/codemirror/themes/gruvboxDark.mjs
vendored
82
src/strudel/codemirror/themes/gruvboxDark.mjs
vendored
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* gruvbox-dark
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-dark.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#282828',
|
||||
lineBackground: '#28282899',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#bdae93',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#3c3836',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#282828',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#b99d555c',
|
||||
selectionMatch: '#b99d555c',
|
||||
lineHighlight: '#baa1602b',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#fb4934' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#8ec07c' },
|
||||
{ tag: [t.variableName], color: '#83a598' },
|
||||
{ tag: [t.function(t.variableName)], color: '#8ec07c', fontStyle: 'bold' },
|
||||
{ tag: [t.labelName], color: '#ebdbb2' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#d3869b' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#ebdbb2' },
|
||||
{ tag: [t.brace], color: '#ebdbb2' },
|
||||
{ tag: [t.annotation], color: '#fb4934d' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#d3869b' },
|
||||
{ tag: [t.typeName, t.className], color: '#fabd2f' },
|
||||
{ tag: [t.operatorKeyword], color: '#fb4934' },
|
||||
{
|
||||
tag: [t.tagName],
|
||||
color: '#8ec07c',
|
||||
fontStyle: 'bold',
|
||||
},
|
||||
{ tag: [t.squareBracket], color: '#fe8019' },
|
||||
{ tag: [t.angleBracket], color: '#83a598' },
|
||||
{ tag: [t.attributeName], color: '#8ec07c' },
|
||||
{ tag: [t.regexp], color: '#8ec07c' },
|
||||
{ tag: [t.quote], color: '#928374' },
|
||||
{ tag: [t.string], color: '#ebdbb2' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#a89984',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#d3869b' },
|
||||
{ tag: [t.meta], color: '#fabd2f' },
|
||||
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#fe8019' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#b8bb26' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#b8bb26' },
|
||||
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#b8bb26' },
|
||||
{
|
||||
tag: [t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#fabd2f',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#fabd2f' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d3869b' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#83a598' },
|
||||
{ tag: [t.contentSeparator], color: '#fb4934' },
|
||||
{ tag: t.invalid, color: '#fe8019', borderBottom: `1px dotted #fb4934d` },
|
||||
],
|
||||
});
|
||||
130
src/strudel/codemirror/themes/gruvboxLight.mjs
vendored
130
src/strudel/codemirror/themes/gruvboxLight.mjs
vendored
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* gruvbox-light
|
||||
* author morhetz
|
||||
* From github.com/codemirror/codemirror5/blob/master/theme/gruvbox-light.css
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fbf1c7',
|
||||
lineBackground: '#fbf1c799',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#ebdbb2',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#ebdbb2',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fbf1c7',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#bdae9391',
|
||||
selectionMatch: '#bdae9391',
|
||||
lineHighlight: '#a37f2238',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#9d0006' },
|
||||
{
|
||||
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
|
||||
color: '#427b58',
|
||||
},
|
||||
{ tag: [t.variableName], color: '#076678' },
|
||||
{ tag: [t.function(t.variableName)], color: '#79740e', fontStyle: 'bold' },
|
||||
{ tag: [t.labelName], color: '#3c3836' },
|
||||
{
|
||||
tag: [t.color, t.constant(t.name), t.standard(t.name)],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#3c3836' },
|
||||
{ tag: [t.brace], color: '#3c3836' },
|
||||
{
|
||||
tag: [t.annotation],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{
|
||||
tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{
|
||||
tag: [t.typeName, t.className],
|
||||
color: '#b57614',
|
||||
},
|
||||
{
|
||||
tag: [t.operator, t.operatorKeyword],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{
|
||||
tag: [t.tagName],
|
||||
color: '#427b58',
|
||||
fontStyle: 'bold',
|
||||
},
|
||||
{
|
||||
tag: [t.squareBracket],
|
||||
color: '#af3a03',
|
||||
},
|
||||
{
|
||||
tag: [t.angleBracket],
|
||||
color: '#076678',
|
||||
},
|
||||
{
|
||||
tag: [t.attributeName],
|
||||
color: '#427b58',
|
||||
},
|
||||
{
|
||||
tag: [t.regexp],
|
||||
color: '#427b58',
|
||||
},
|
||||
{
|
||||
tag: [t.quote],
|
||||
color: '#928374',
|
||||
},
|
||||
{ tag: [t.string], color: '#3c3836' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#7c6f64',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{
|
||||
tag: [t.url, t.escape, t.special(t.string)],
|
||||
color: '#8f3f71',
|
||||
},
|
||||
{ tag: [t.meta], color: '#b57614' },
|
||||
{ tag: [t.comment], color: '#928374', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#af3a03' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#79740e' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#79740e' },
|
||||
{ tag: [t.heading1, t.heading2], fontWeight: 'bold', color: '#79740e' },
|
||||
{
|
||||
tag: [t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#b57614',
|
||||
},
|
||||
{
|
||||
tag: [t.heading5, t.heading6],
|
||||
color: '#b57614',
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#8f3f71' },
|
||||
{
|
||||
tag: [t.processingInstruction, t.inserted],
|
||||
color: '#076678',
|
||||
},
|
||||
{
|
||||
tag: [t.contentSeparator],
|
||||
color: '#9d0006',
|
||||
},
|
||||
{ tag: t.invalid, color: '#af3a03', borderBottom: `1px dotted #9d0006` },
|
||||
],
|
||||
});
|
||||
77
src/strudel/codemirror/themes/materialDark.mjs
vendored
77
src/strudel/codemirror/themes/materialDark.mjs
vendored
|
|
@ -1,77 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#212121',
|
||||
lineBackground: '#21212199',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#212121',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#111111',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#212121',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f063',
|
||||
selectionMatch: '#d7d4f063',
|
||||
gutterBackground: '#212121',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#333333',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#cf6edf' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#56c8d8' },
|
||||
{ tag: [t.propertyName], color: '#82AAFF' },
|
||||
{ tag: [t.variableName], color: '#bdbdbd' },
|
||||
{ tag: [t.function(t.variableName)], color: '#82AAFF' },
|
||||
{ tag: [t.labelName], color: '#cf6edf' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#facf4e' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#56c8d8' },
|
||||
{ tag: [t.brace], color: '#cf6edf' },
|
||||
{ tag: [t.annotation], color: '#f07178' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#f07178' },
|
||||
{ tag: [t.typeName, t.className], color: '#f07178' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#82AAFF' },
|
||||
{ tag: [t.tagName], color: '#99d066' },
|
||||
{ tag: [t.squareBracket], color: '#f07178' },
|
||||
{ tag: [t.angleBracket], color: '#606f7a' },
|
||||
{ tag: [t.attributeName], color: '#bdbdbd' },
|
||||
{ tag: [t.regexp], color: '#f07178' },
|
||||
{ tag: [t.quote], color: '#6abf69' },
|
||||
{ tag: [t.string], color: '#99d066' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#56c8d8',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#facf4e' },
|
||||
{ tag: [t.meta], color: '#707d8b' },
|
||||
{ tag: [t.comment], color: '#707d8b', fontStyle: 'italic' },
|
||||
{ tag: t.monospace, color: '#bdbdbd' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#f07178' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#99d066' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#facf4e' },
|
||||
{ tag: t.heading1, fontWeight: 'bold', color: '#facf4e' },
|
||||
{
|
||||
tag: [t.heading2, t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#facf4e',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#facf4e' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#56c8d8' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#f07178' },
|
||||
{ tag: [t.contentSeparator], color: '#56c8d8' },
|
||||
{ tag: t.invalid, color: '#606f7a', borderBottom: `1px dotted #f07178` },
|
||||
],
|
||||
});
|
||||
52
src/strudel/codemirror/themes/materialLight.mjs
vendored
52
src/strudel/codemirror/themes/materialLight.mjs
vendored
|
|
@ -1,52 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#FAFAFA',
|
||||
lineBackground: '#FAFAFA99',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#FAFAFA',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#FAFAFA',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#80CBC440',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#39ADB5' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#90A4AE' },
|
||||
{ tag: [t.propertyName], color: '#6182B8' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#91B859' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#6182B8' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#39ADB5' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#90A4AE' },
|
||||
{ tag: [t.className], color: '#E2931D' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#F76D47' },
|
||||
{ tag: [t.typeName], color: '#E2931D', fontStyle: '#E2931D' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#39ADB5' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#91B859' },
|
||||
{ tag: [t.meta, t.comment], color: '#90A4AE' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#39ADB5' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#90A4AE' },
|
||||
{ tag: t.invalid, color: '#E5393570' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
45
src/strudel/codemirror/themes/monokai.mjs
vendored
45
src/strudel/codemirror/themes/monokai.mjs
vendored
|
|
@ -1,45 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#272822',
|
||||
lineBackground: '#27282299',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#272822',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#0000003b',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#bababa' },
|
||||
{ tag: [t.comment, t.documentMeta], color: '#8292a2' },
|
||||
{ tag: [t.number, t.bool, t.null, t.atom], color: '#ae81ff' },
|
||||
{ tag: [t.attributeValue, t.className, t.name], color: '#e6db74' },
|
||||
{ tag: [t.propertyName, t.attributeName], color: '#a6e22e' },
|
||||
{ tag: [t.variableName], color: '#9effff' },
|
||||
{ tag: [t.squareBracket], color: '#bababa' },
|
||||
{ tag: [t.string, t.special(t.brace)], color: '#e6db74' },
|
||||
{ tag: [t.regexp, t.className, t.typeName, t.definition(t.typeName)], color: '#66d9ef' },
|
||||
{
|
||||
tag: [t.definition(t.variableName), t.definition(t.propertyName), t.function(t.variableName)],
|
||||
color: '#a6e22e',
|
||||
},
|
||||
// { tag: t.keyword, color: '#f92672' },
|
||||
{ tag: [t.keyword, t.definitionKeyword, t.modifier, t.tagName, t.angleBracket], color: '#f92672' },
|
||||
],
|
||||
});
|
||||
50
src/strudel/codemirror/themes/noctisLilac.mjs
vendored
50
src/strudel/codemirror/themes/noctisLilac.mjs
vendored
|
|
@ -1,50 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#f2f1f8',
|
||||
lineBackground: '#f2f1f899',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#e1def3',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#f2f1f8',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#16067911',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.comment, color: '#9995b7' },
|
||||
{
|
||||
tag: t.keyword,
|
||||
color: '#ff5792',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
{ tag: [t.definitionKeyword, t.modifier], color: '#ff5792' },
|
||||
{ tag: [t.className, t.tagName, t.definition(t.typeName)], color: '#0094f0' },
|
||||
{ tag: [t.number, t.bool, t.null, t.special(t.brace)], color: '#5842ff' },
|
||||
{ tag: [t.definition(t.propertyName), t.function(t.variableName)], color: '#0095a8' },
|
||||
{ tag: t.typeName, color: '#b3694d' },
|
||||
{ tag: [t.propertyName, t.variableName], color: '#fa8900' },
|
||||
{ tag: t.operator, color: '#ff5792' },
|
||||
{ tag: t.self, color: '#e64100' },
|
||||
{ tag: [t.string, t.regexp], color: '#00b368' },
|
||||
{ tag: [t.paren, t.bracket], color: '#0431fa' },
|
||||
{ tag: t.labelName, color: '#00bdd6' },
|
||||
{ tag: t.attributeName, color: '#e64100' },
|
||||
{ tag: t.angleBracket, color: '#9995b7' },
|
||||
],
|
||||
});
|
||||
78
src/strudel/codemirror/themes/nord.mjs
vendored
78
src/strudel/codemirror/themes/nord.mjs
vendored
|
|
@ -1,78 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#2e3440',
|
||||
lineBackground: '#2e344099',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#3b4252',
|
||||
selectionMatch: '#e5e9f0',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a',
|
||||
};
|
||||
|
||||
// Colors from https://www.nordtheme.com/docs/colors-and-palettes
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#2e3440',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#00000073',
|
||||
selectionMatch: '#00000073',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a29',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#5e81ac' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: '#88c0d0' },
|
||||
{ tag: [t.variableName], color: '#8fbcbb' },
|
||||
{ tag: [t.function(t.variableName)], color: '#8fbcbb' },
|
||||
{ tag: [t.labelName], color: '#81a1c1' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#5e81ac' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#a3be8c' },
|
||||
{ tag: [t.brace], color: '#8fbcbb' },
|
||||
{ tag: [t.annotation], color: '#d30102' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b48ead' },
|
||||
{ tag: [t.typeName, t.className], color: '#ebcb8b' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#a3be8c' },
|
||||
{ tag: [t.tagName], color: '#b48ead' },
|
||||
{ tag: [t.squareBracket], color: '#bf616a' },
|
||||
{ tag: [t.angleBracket], color: '#d08770' },
|
||||
{ tag: [t.attributeName], color: '#ebcb8b' },
|
||||
{ tag: [t.regexp], color: '#5e81ac' },
|
||||
{ tag: [t.quote], color: '#b48ead' },
|
||||
{ tag: [t.string], color: '#a3be8c' },
|
||||
{
|
||||
tag: t.link,
|
||||
color: '#a3be8c',
|
||||
textDecoration: 'underline',
|
||||
textUnderlinePosition: 'under',
|
||||
},
|
||||
{ tag: [t.url, t.escape, t.special(t.string)], color: '#8fbcbb' },
|
||||
{ tag: [t.meta], color: '#88c0d0' },
|
||||
{ tag: [t.monospace], color: '#d8dee9', fontStyle: 'italic' },
|
||||
{ tag: [t.comment], color: '#4c566a', fontStyle: 'italic' },
|
||||
{ tag: t.strong, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic', color: '#5e81ac' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.special(t.heading1), fontWeight: 'bold', color: '#5e81ac' },
|
||||
{ tag: t.heading1, fontWeight: 'bold', color: '#5e81ac' },
|
||||
{
|
||||
tag: [t.heading2, t.heading3, t.heading4],
|
||||
fontWeight: 'bold',
|
||||
color: '#5e81ac',
|
||||
},
|
||||
{ tag: [t.heading5, t.heading6], color: '#5e81ac' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d08770' },
|
||||
{ tag: [t.processingInstruction, t.inserted], color: '#8fbcbb' },
|
||||
{ tag: [t.contentSeparator], color: '#ebcb8b' },
|
||||
{ tag: t.invalid, color: '#434c5e', borderBottom: `1px dotted #d30102` },
|
||||
],
|
||||
});
|
||||
39
src/strudel/codemirror/themes/red-text.mjs
vendored
39
src/strudel/codemirror/themes/red-text.mjs
vendored
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#000000', '#ff5356', '#bd312a', '#54636D', '#171717'];
|
||||
|
||||
export const settings = {
|
||||
background: hex[0],
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[2],
|
||||
selection: hex[4],
|
||||
selectionMatch: hex[0],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[3],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[2],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[1] },
|
||||
{ tag: t.comment, color: hex[3] },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[2] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[1] },
|
||||
{ tag: t.keyword, color: hex[2] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[1] },
|
||||
],
|
||||
});
|
||||
79
src/strudel/codemirror/themes/solarizedDark.mjs
vendored
79
src/strudel/codemirror/themes/solarizedDark.mjs
vendored
|
|
@ -1,79 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#002b36',
|
||||
lineBackground: '#002b3699',
|
||||
foreground: '#93a1a1',
|
||||
caret: '#839496',
|
||||
selection: '#173541',
|
||||
selectionMatch: '#aafe661a',
|
||||
gutterBackground: '#00252f',
|
||||
gutterForeground: '#839496',
|
||||
lineHighlight: '#173541',
|
||||
};
|
||||
|
||||
const c = {
|
||||
background: '#002B36',
|
||||
foreground: '#839496',
|
||||
selection: '#004454AA',
|
||||
selectionMatch: '#005A6FAA',
|
||||
cursor: '#D30102',
|
||||
dropdownBackground: '#00212B',
|
||||
dropdownBorder: '#2AA19899',
|
||||
activeLine: '#00cafe11',
|
||||
matchingBracket: '#073642',
|
||||
keyword: '#859900',
|
||||
storage: '#93A1A1',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#859900',
|
||||
class: '#268BD2',
|
||||
number: '#D33682',
|
||||
comment: '#586E75',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
tag: '#268BD2',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: c.background,
|
||||
foreground: c.foreground,
|
||||
caret: c.cursor,
|
||||
selection: c.selection,
|
||||
selectionMatch: c.selection,
|
||||
gutterBackground: c.background,
|
||||
gutterForeground: c.foreground,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: c.activeLine,
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: c.keyword },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
|
||||
{ tag: [t.propertyName], color: c.function },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
|
||||
{ tag: [t.definition(t.name), t.separator], color: c.variable },
|
||||
{ tag: [t.className], color: c.class },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
|
||||
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
|
||||
{ tag: [t.meta, t.comment], color: c.comment },
|
||||
{ tag: t.tagName, color: c.tag },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
|
||||
{ tag: t.invalid, color: c.invalid },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
82
src/strudel/codemirror/themes/solarizedLight.mjs
vendored
82
src/strudel/codemirror/themes/solarizedLight.mjs
vendored
|
|
@ -1,82 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
// this is slightly different from https://thememirror.net/solarized-light
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fdf6e3',
|
||||
lineBackground: '#fdf6e399',
|
||||
foreground: '#657b83',
|
||||
caret: '#586e75',
|
||||
selection: '#dfd9c8',
|
||||
selectionMatch: '#dfd9c8',
|
||||
gutterBackground: '#00000010',
|
||||
gutterForeground: '#657b83',
|
||||
lineHighlight: '#dfd9c8',
|
||||
};
|
||||
|
||||
const c = {
|
||||
background: '#FDF6E3',
|
||||
foreground: '#657B83',
|
||||
selection: '#EEE8D5',
|
||||
selectionMatch: '#EEE8D5',
|
||||
cursor: '#657B83',
|
||||
dropdownBackground: '#EEE8D5',
|
||||
dropdownBorder: '#D3AF86',
|
||||
activeLine: '#3d392d11',
|
||||
matchingBracket: '#EEE8D5',
|
||||
keyword: '#859900',
|
||||
storage: '#586E75',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#859900',
|
||||
class: '#268BD2',
|
||||
number: '#D33682',
|
||||
comment: '#93A1A1',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
tag: '#268BD2',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: c.background,
|
||||
foreground: c.foreground,
|
||||
caret: c.cursor,
|
||||
selection: c.selection,
|
||||
selectionMatch: c.selectionMatch,
|
||||
gutterBackground: c.background,
|
||||
gutterForeground: c.foreground,
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: c.activeLine,
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: c.keyword },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: c.variable },
|
||||
{ tag: [t.propertyName], color: c.function },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: c.string },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: c.function },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: c.constant },
|
||||
{ tag: [t.definition(t.name), t.separator], color: c.variable },
|
||||
{ tag: [t.className], color: c.class },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: c.number },
|
||||
{ tag: [t.typeName], color: c.type, fontStyle: c.type },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: c.keyword },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: c.regexp },
|
||||
{ tag: [t.meta, t.comment], color: c.comment },
|
||||
{ tag: t.tagName, color: c.tag },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: c.heading },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: c.variable },
|
||||
{ tag: t.invalid, color: c.invalid },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
39
src/strudel/codemirror/themes/sonic-pink.mjs
vendored
39
src/strudel/codemirror/themes/sonic-pink.mjs
vendored
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Atom One
|
||||
* Atom One dark syntax theme
|
||||
*
|
||||
* https://github.com/atom/one-dark-syntax
|
||||
*/
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
const hex = ['#1e1e1e', '#fbde2d', '#ff1493', '#4c83ff', '#ededed', '#cccccc', '#ffffff30', '#dc2f8c'];
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
lineBackground: 'transparent',
|
||||
foreground: hex[4],
|
||||
selection: hex[6],
|
||||
gutterBackground: hex[0],
|
||||
gutterForeground: hex[5],
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: hex[0],
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{
|
||||
tag: [t.function(t.variableName), t.function(t.propertyName), t.url, t.processingInstruction],
|
||||
color: hex[4],
|
||||
},
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: hex[3] },
|
||||
|
||||
{ tag: t.comment, color: '#54636D' },
|
||||
{ tag: [t.variableName, t.propertyName, t.labelName], color: hex[4] },
|
||||
{ tag: [t.attributeName, t.number], color: hex[3] },
|
||||
{ tag: t.keyword, color: hex[1] },
|
||||
{ tag: [t.string, t.regexp, t.special(t.propertyName)], color: hex[2] },
|
||||
],
|
||||
});
|
||||
49
src/strudel/codemirror/themes/strudel-theme.mjs
vendored
49
src/strudel/codemirror/themes/strudel-theme.mjs
vendored
|
|
@ -1,49 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#222',
|
||||
lineBackground: '#22222299',
|
||||
foreground: '#fff',
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#89ddff' },
|
||||
{ tag: t.labelName, color: '#89ddff' },
|
||||
{ tag: t.keyword, color: '#c792ea' },
|
||||
{ tag: t.operator, color: '#89ddff' },
|
||||
{ tag: t.special(t.variableName), color: '#eeffff' },
|
||||
// { tag: t.typeName, color: '#f07178' }, // original
|
||||
{ tag: t.typeName, color: '#c3e88d' },
|
||||
{ tag: t.atom, color: '#f78c6c' },
|
||||
// { tag: t.number, color: '#ff5370' }, // original
|
||||
{ tag: t.number, color: '#c3e88d' },
|
||||
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
||||
{ tag: t.string, color: '#c3e88d' },
|
||||
// { tag: t.special(t.string), color: '#f07178' }, // original
|
||||
{ tag: t.special(t.string), color: '#c3e88d' },
|
||||
{ tag: t.comment, color: '#7d8799' },
|
||||
// { tag: t.variableName, color: '#f07178' }, // original
|
||||
{ tag: t.variableName, color: '#c792ea' },
|
||||
// { tag: t.tagName, color: '#ff5370' }, // original
|
||||
{ tag: t.tagName, color: '#c3e88d' },
|
||||
{ tag: t.bracket, color: '#525154' },
|
||||
// { tag: t.bracket, color: '#a2a1a4' }, // original
|
||||
{ tag: t.meta, color: '#ffcb6b' },
|
||||
{ tag: t.attributeName, color: '#c792ea' },
|
||||
{ tag: t.propertyName, color: '#c792ea' },
|
||||
|
||||
{ tag: t.className, color: '#decb6b' },
|
||||
{ tag: t.invalid, color: '#ffffff' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#82aaff' },
|
||||
],
|
||||
});
|
||||
43
src/strudel/codemirror/themes/sublime.mjs
vendored
43
src/strudel/codemirror/themes/sublime.mjs
vendored
|
|
@ -1,43 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#303841',
|
||||
lineBackground: '#30384199',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#303841',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.labelName, color: '#A2A9B5' },
|
||||
{ tag: [t.meta, t.comment], color: '#A2A9B5' },
|
||||
{ tag: [t.attributeName, t.keyword], color: '#B78FBA' },
|
||||
{ tag: t.function(t.variableName), color: '#5AB0B0' },
|
||||
{ tag: [t.string, t.regexp, t.attributeValue], color: '#99C592' },
|
||||
{ tag: t.operator, color: '#f47954' },
|
||||
// { tag: t.moduleKeyword, color: 'red' },
|
||||
{ tag: [t.tagName, t.modifier], color: '#E35F63' },
|
||||
{ tag: [t.number, t.definition(t.tagName), t.className, t.definition(t.variableName)], color: '#fbac52' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#E35F63' },
|
||||
{ tag: t.variableName, color: '#539ac4' },
|
||||
{ tag: [t.propertyName, t.typeName], color: '#629ccd' },
|
||||
{ tag: t.propertyName, color: '#36b7b5' },
|
||||
],
|
||||
});
|
||||
51
src/strudel/codemirror/themes/teletext.mjs
vendored
51
src/strudel/codemirror/themes/teletext.mjs
vendored
|
|
@ -1,51 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
let colorA = '#6edee4';
|
||||
//let colorB = 'magenta';
|
||||
let colorB = 'white';
|
||||
let colorC = 'red';
|
||||
let colorD = '#f8fc55';
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
foreground: colorA, // whats that?
|
||||
caret: colorC,
|
||||
selection: colorD,
|
||||
selectionMatch: colorA,
|
||||
lineHighlight: '#6edee440', // panel bg
|
||||
lineBackground: '#00000040',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
// customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
|
||||
let punctuation = colorD;
|
||||
let mini = colorB;
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: colorB },
|
||||
{ tag: t.keyword, color: colorA },
|
||||
{ tag: t.operator, color: mini },
|
||||
{ tag: t.special(t.variableName), color: colorA },
|
||||
{ tag: t.typeName, color: colorA },
|
||||
{ tag: t.atom, color: colorA },
|
||||
{ tag: t.number, color: mini },
|
||||
{ tag: t.definition(t.variableName), color: colorA },
|
||||
{ tag: t.string, color: mini },
|
||||
{ tag: t.special(t.string), color: mini },
|
||||
{ tag: t.comment, color: punctuation },
|
||||
{ tag: t.variableName, color: colorA },
|
||||
{ tag: t.tagName, color: colorA },
|
||||
{ tag: t.bracket, color: punctuation },
|
||||
{ tag: t.meta, color: colorA },
|
||||
{ tag: t.attributeName, color: colorA },
|
||||
{ tag: t.propertyName, color: colorA }, // methods
|
||||
{ tag: t.className, color: colorA },
|
||||
{ tag: t.invalid, color: colorC },
|
||||
{ tag: [t.unit, t.punctuation], color: punctuation },
|
||||
],
|
||||
});
|
||||
37
src/strudel/codemirror/themes/terminal.mjs
vendored
37
src/strudel/codemirror/themes/terminal.mjs
vendored
|
|
@ -1,37 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
caret: '#41FF00',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
],
|
||||
});
|
||||
42
src/strudel/codemirror/themes/theme-helper.mjs
vendored
42
src/strudel/codemirror/themes/theme-helper.mjs
vendored
|
|
@ -1,42 +0,0 @@
|
|||
import { EditorView } from '@codemirror/view';
|
||||
import { syntaxHighlighting } from '@codemirror/language';
|
||||
import { HighlightStyle } from '@codemirror/language';
|
||||
|
||||
export const createTheme = ({ theme, settings, styles }) => {
|
||||
const _theme = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
color: settings.foreground,
|
||||
backgroundColor: settings.background,
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: settings.gutterBackground,
|
||||
color: settings.gutterForeground,
|
||||
//borderRightColor: settings.gutterBorder
|
||||
},
|
||||
'.cm-content': {
|
||||
caretColor: settings.caret,
|
||||
},
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: settings.caret,
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
// color: settings.gutterActiveForeground
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: settings.lineHighlight,
|
||||
},
|
||||
'&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection':
|
||||
{
|
||||
background: settings.selection + ' !important',
|
||||
},
|
||||
'& .cm-selectionMatch': {
|
||||
backgroundColor: settings.selectionMatch,
|
||||
},
|
||||
},
|
||||
{ dark: theme === 'dark' },
|
||||
);
|
||||
const highlightStyle = HighlightStyle.define(styles);
|
||||
return [_theme, syntaxHighlighting(highlightStyle)];
|
||||
};
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#24283b',
|
||||
lineBackground: '#24283b99',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#1f2335',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e42',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#24283b',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#343b5f',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e427a',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#bb9af7' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
|
||||
{ tag: [t.propertyName], color: '#7aa2f7' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
|
||||
{ tag: [t.className], color: '#c0caf5' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
|
||||
{ tag: [t.typeName], color: '#2ac3de', fontStyle: '#2ac3de' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
|
||||
{ tag: [t.meta, t.comment], color: '#565f89' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
|
||||
{ tag: t.invalid, color: '#ff5370' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
52
src/strudel/codemirror/themes/tokyoNight.mjs
vendored
52
src/strudel/codemirror/themes/tokyoNight.mjs
vendored
|
|
@ -1,52 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#1a1b26',
|
||||
lineBackground: '#1a1b2699',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#1e202e',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#1a1b26',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#474b6611',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#bb9af7' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#c0caf5' },
|
||||
{ tag: [t.propertyName], color: '#7aa2f7' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#9ece6a' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#7aa2f7' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#bb9af7' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#c0caf5' },
|
||||
{ tag: [t.className], color: '#c0caf5' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#ff9e64' },
|
||||
{ tag: [t.typeName], color: '#0db9d7' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#bb9af7' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#b4f9f8' },
|
||||
{ tag: [t.meta, t.comment], color: '#444b6a' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#89ddff' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#c0caf5' },
|
||||
{ tag: t.invalid, color: '#ff5370' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
53
src/strudel/codemirror/themes/tokyoNightDay.mjs
vendored
53
src/strudel/codemirror/themes/tokyoNightDay.mjs
vendored
|
|
@ -1,53 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#e1e2e7',
|
||||
lineBackground: '#e1e2e799',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#e1e2e7',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#007197' },
|
||||
{ tag: [t.name, t.deleted, t.character, t.macroName], color: '#3760bf' },
|
||||
{ tag: [t.propertyName], color: '#3760bf' },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: '#587539' },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: '#3760bf' },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: '#3760bf' },
|
||||
{ tag: [t.definition(t.name), t.separator], color: '#3760bf' },
|
||||
{ tag: [t.className], color: '#3760bf' },
|
||||
{ tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: '#b15c00' },
|
||||
{ tag: [t.typeName], color: '#007197', fontStyle: '#007197' },
|
||||
{ tag: [t.operator, t.operatorKeyword], color: '#007197' },
|
||||
{ tag: [t.url, t.escape, t.regexp, t.link], color: '#587539' },
|
||||
{ tag: [t.meta, t.comment], color: '#848cb5' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.link, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#b15c00' },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: '#3760bf' },
|
||||
{ tag: t.invalid, color: '#f52a65' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
],
|
||||
});
|
||||
80
src/strudel/codemirror/themes/vscodeDark.mjs
vendored
80
src/strudel/codemirror/themes/vscodeDark.mjs
vendored
|
|
@ -1,80 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#1e1e1e',
|
||||
lineBackground: '#1e1e1e99',
|
||||
foreground: '#fff',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#fff',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [
|
||||
t.keyword,
|
||||
t.operatorKeyword,
|
||||
t.modifier,
|
||||
t.color,
|
||||
t.constant(t.name),
|
||||
t.standard(t.name),
|
||||
t.standard(t.tagName),
|
||||
t.special(t.brace),
|
||||
t.atom,
|
||||
t.bool,
|
||||
t.special(t.variableName),
|
||||
],
|
||||
color: '#569cd6',
|
||||
},
|
||||
{ tag: [t.controlKeyword, t.moduleKeyword], color: '#c586c0' },
|
||||
{
|
||||
tag: [
|
||||
t.name,
|
||||
t.deleted,
|
||||
t.character,
|
||||
t.macroName,
|
||||
t.propertyName,
|
||||
t.variableName,
|
||||
t.labelName,
|
||||
t.definition(t.name),
|
||||
],
|
||||
color: '#9cdcfe',
|
||||
},
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#9cdcfe' },
|
||||
{
|
||||
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
|
||||
color: '#4ec9b0',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#dcdcaa' },
|
||||
{ tag: [t.number], color: '#b5cea8' },
|
||||
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#d4d4d4' },
|
||||
{ tag: [t.regexp], color: '#d16969' },
|
||||
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#ce9178' },
|
||||
{ tag: [t.angleBracket], color: '#808080' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: [t.meta, t.comment], color: '#6a9955' },
|
||||
{ tag: t.link, color: '#6a9955', textDecoration: 'underline' },
|
||||
{ tag: t.invalid, color: '#ff0000' },
|
||||
],
|
||||
});
|
||||
81
src/strudel/codemirror/themes/vscodeLight.mjs
vendored
81
src/strudel/codemirror/themes/vscodeLight.mjs
vendored
|
|
@ -1,81 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
background: '#ffffff',
|
||||
lineBackground: '#ffffff50',
|
||||
foreground: '#383a42',
|
||||
caret: '#000',
|
||||
selection: '#add6ff',
|
||||
selectionMatch: '#a8ac94',
|
||||
lineHighlight: '#99999926',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#237893',
|
||||
gutterActiveForeground: '#0b216f',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#ffffff',
|
||||
foreground: '#383a42',
|
||||
caret: '#000',
|
||||
selection: '#add6ff',
|
||||
selectionMatch: '#a8ac94',
|
||||
lineHighlight: '#99999926',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#237893',
|
||||
gutterActiveForeground: '#0b216f',
|
||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
tag: [
|
||||
t.keyword,
|
||||
t.operatorKeyword,
|
||||
t.modifier,
|
||||
t.color,
|
||||
t.constant(t.name),
|
||||
t.standard(t.name),
|
||||
t.standard(t.tagName),
|
||||
t.special(t.brace),
|
||||
t.atom,
|
||||
t.bool,
|
||||
t.special(t.variableName),
|
||||
],
|
||||
color: '#0000ff',
|
||||
},
|
||||
{ tag: [t.moduleKeyword, t.controlKeyword], color: '#af00db' },
|
||||
{
|
||||
tag: [
|
||||
t.name,
|
||||
t.deleted,
|
||||
t.character,
|
||||
t.macroName,
|
||||
t.propertyName,
|
||||
t.variableName,
|
||||
t.labelName,
|
||||
t.definition(t.name),
|
||||
],
|
||||
color: '#0070c1',
|
||||
},
|
||||
{ tag: t.heading, fontWeight: 'bold', color: '#0070c1' },
|
||||
{
|
||||
tag: [t.typeName, t.className, t.tagName, t.number, t.changed, t.annotation, t.self, t.namespace],
|
||||
color: '#267f99',
|
||||
},
|
||||
{ tag: [t.function(t.variableName), t.function(t.propertyName)], color: '#795e26' },
|
||||
{ tag: [t.number], color: '#098658' },
|
||||
{ tag: [t.operator, t.punctuation, t.separator, t.url, t.escape, t.regexp], color: '#383a42' },
|
||||
{ tag: [t.regexp], color: '#af00db' },
|
||||
{ tag: [t.special(t.string), t.processingInstruction, t.string, t.inserted], color: '#a31515' },
|
||||
{ tag: [t.angleBracket], color: '#383a42' },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: [t.meta, t.comment], color: '#008000' },
|
||||
{ tag: t.link, color: '#4078f2', textDecoration: 'underline' },
|
||||
{ tag: t.invalid, color: '#e45649' },
|
||||
],
|
||||
});
|
||||
39
src/strudel/codemirror/themes/whitescreen.mjs
vendored
39
src/strudel/codemirror/themes/whitescreen.mjs
vendored
|
|
@ -1,39 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
foreground: 'black', // whats that?
|
||||
caret: 'black',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#cccccc50',
|
||||
lineBackground: '#ffffff50',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: 'black',
|
||||
light: true,
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.labelName, color: 'inherit' },
|
||||
{ tag: t.keyword, color: 'inherit' },
|
||||
{ tag: t.operator, color: 'inherit' },
|
||||
{ tag: t.special(t.variableName), color: 'inherit' },
|
||||
{ tag: t.typeName, color: 'inherit' },
|
||||
{ tag: t.atom, color: 'inherit' },
|
||||
{ tag: t.number, color: 'inherit' },
|
||||
{ tag: t.definition(t.variableName), color: 'inherit' },
|
||||
{ tag: t.string, color: 'inherit' },
|
||||
{ tag: t.special(t.string), color: 'inherit' },
|
||||
{ tag: t.comment, color: 'inherit' },
|
||||
{ tag: t.variableName, color: 'inherit' },
|
||||
{ tag: t.tagName, color: 'inherit' },
|
||||
{ tag: t.bracket, color: 'inherit' },
|
||||
{ tag: t.meta, color: 'inherit' },
|
||||
{ tag: t.attributeName, color: 'inherit' },
|
||||
{ tag: t.propertyName, color: 'inherit' },
|
||||
{ tag: t.className, color: 'inherit' },
|
||||
{ tag: t.invalid, color: 'inherit' },
|
||||
],
|
||||
});
|
||||
38
src/strudel/codemirror/themes/xcodeLight.mjs
vendored
38
src/strudel/codemirror/themes/xcodeLight.mjs
vendored
|
|
@ -1,38 +0,0 @@
|
|||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from './theme-helper.mjs';
|
||||
|
||||
export const settings = {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#EDF4FF',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: '#fff',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#d5e6ff69',
|
||||
},
|
||||
styles: [
|
||||
{ tag: [t.comment, t.quote], color: '#707F8D' },
|
||||
{ tag: [t.typeName, t.typeOperator], color: '#aa0d91' },
|
||||
{ tag: [t.keyword], color: '#aa0d91', fontWeight: 'bold' },
|
||||
{ tag: [t.string, t.meta], color: '#D23423' },
|
||||
{ tag: [t.name], color: '#032f62' },
|
||||
{ tag: [t.typeName], color: '#522BB2' },
|
||||
{ tag: [t.variableName], color: '#23575C' },
|
||||
{ tag: [t.definition(t.variableName)], color: '#327A9E' },
|
||||
{ tag: [t.regexp, t.link], color: '#0e0eff' },
|
||||
],
|
||||
});
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import { hoverTooltip } from '@codemirror/view';
|
||||
import jsdoc from '../../doc.json';
|
||||
import { Autocomplete, getSynonymDoc } from './autocomplete.mjs';
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
|
||||
let ctrlDown = false;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// Record Control key event to trigger or block the tooltip depending on the state
|
||||
window.addEventListener(
|
||||
'keyup',
|
||||
function (e) {
|
||||
if (e.key == 'Control') {
|
||||
ctrlDown = false;
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
window.addEventListener(
|
||||
'keydown',
|
||||
function (e) {
|
||||
if (e.key == 'Control') {
|
||||
ctrlDown = true;
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
export const strudelTooltip = hoverTooltip(
|
||||
(view, pos, side) => {
|
||||
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
|
||||
if (!ctrlDown) {
|
||||
return null;
|
||||
}
|
||||
let { from, to, text } = view.state.doc.lineAt(pos);
|
||||
let start = pos,
|
||||
end = pos;
|
||||
while (start > from && /\w/.test(text[start - from - 1])) {
|
||||
start--;
|
||||
}
|
||||
while (end < to && /\w/.test(text[end - from])) {
|
||||
end++;
|
||||
}
|
||||
if ((start == pos && side < 0) || (end == pos && side > 0)) {
|
||||
return null;
|
||||
}
|
||||
let word = text.slice(start - from, end - from);
|
||||
// Get entry from Strudel documentation
|
||||
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
|
||||
if (!entry) {
|
||||
// Try for synonyms
|
||||
const doc = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0];
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
entry = getSynonymDoc(doc, word);
|
||||
}
|
||||
|
||||
return {
|
||||
pos: start,
|
||||
end,
|
||||
above: false,
|
||||
arrow: true,
|
||||
create(view) {
|
||||
let dom = document.createElement('div');
|
||||
dom.className = 'strudel-tooltip';
|
||||
const ac = Autocomplete(entry);
|
||||
dom.appendChild(ac);
|
||||
return { dom };
|
||||
},
|
||||
};
|
||||
},
|
||||
{ hoverTime: 10 },
|
||||
);
|
||||
|
||||
export const isTooltipEnabled = (on) => (on ? strudelTooltip : []);
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import { dependencies } from './package.json';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'index.mjs'),
|
||||
formats: ['es'],
|
||||
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [...Object.keys(dependencies)],
|
||||
},
|
||||
target: 'esnext',
|
||||
},
|
||||
});
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
||||
import { getWidgetID, registerWidgetType } from '@strudel/transpiler';
|
||||
import { Pattern } from '@strudel/core';
|
||||
|
||||
export const addWidget = StateEffect.define({
|
||||
map: ({ from, to }, change) => {
|
||||
return { from: change.mapPos(from), to: change.mapPos(to) };
|
||||
},
|
||||
});
|
||||
|
||||
export const updateWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: addWidget.of(widgets) });
|
||||
};
|
||||
|
||||
function getWidgets(widgetConfigs) {
|
||||
return (
|
||||
widgetConfigs
|
||||
// codemirror throws an error if we don't sort
|
||||
.sort((a, b) => a.to - b.to)
|
||||
.map((widgetConfig) => {
|
||||
return Decoration.widget({
|
||||
widget: new BlockWidget(widgetConfig),
|
||||
side: 0,
|
||||
block: true,
|
||||
}).range(widgetConfig.to);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const widgetField = StateField.define(
|
||||
/* <DecorationSet> */ {
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(widgets, tr) {
|
||||
widgets = widgets.map(tr.changes);
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(addWidget)) {
|
||||
try {
|
||||
widgets = widgets.update({
|
||||
filter: () => false,
|
||||
add: getWidgets(e.value),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('err', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return widgets;
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
},
|
||||
);
|
||||
|
||||
const widgetElements = {};
|
||||
export function setWidget(id, el) {
|
||||
widgetElements[id] = el;
|
||||
el.id = id;
|
||||
}
|
||||
|
||||
export class BlockWidget extends WidgetType {
|
||||
constructor(widgetConfig) {
|
||||
super();
|
||||
this.widgetConfig = widgetConfig;
|
||||
}
|
||||
eq() {
|
||||
return true;
|
||||
}
|
||||
toDOM() {
|
||||
const id = getWidgetID(this.widgetConfig);
|
||||
const el = widgetElements[id];
|
||||
return el;
|
||||
}
|
||||
ignoreEvent(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const widgetPlugin = [widgetField];
|
||||
|
||||
// widget implementer API to create a new widget type
|
||||
export function registerWidget(type, fn) {
|
||||
registerWidgetType(type);
|
||||
if (fn) {
|
||||
Pattern.prototype[type] = function (id, options = { fold: 1 }) {
|
||||
// fn is expected to create a dom element and call setWidget(id, el);
|
||||
// fn should also return the pattern
|
||||
return fn(id, options, this);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// wire up @strudel/draw functions
|
||||
|
||||
function getCanvasWidget(id, options = {}) {
|
||||
const { width = 500, height = 60, pixelRatio = window.devicePixelRatio } = options;
|
||||
let canvas = document.getElementById(id) || document.createElement('canvas');
|
||||
canvas.width = width * pixelRatio;
|
||||
canvas.height = height * pixelRatio;
|
||||
canvas.style.width = width + 'px';
|
||||
canvas.style.height = height + 'px';
|
||||
setWidget(id, canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
registerWidget('_pianoroll', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.tag(id).pianoroll({ fold: 1, ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_punchcard', (id, options = {}, pat) => {
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.tag(id).punchcard({ fold: 1, ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_spiral', (id, options = {}, pat) => {
|
||||
let _size = options.size || 275;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.tag(id).spiral({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_scope', (id, options = {}, pat) => {
|
||||
options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.tag(id).scope({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_pitchwheel', (id, options = {}, pat) => {
|
||||
let _size = options.size || 200;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.pitchwheel({ ...options, ctx, id });
|
||||
});
|
||||
|
||||
registerWidget('_spectrum', (id, options = {}, pat) => {
|
||||
let _size = options.size || 200;
|
||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||
return pat.spectrum({ ...options, ctx, id });
|
||||
});
|
||||
|
|
@ -4,34 +4,34 @@
|
|||
// import createClock from './zyklus.mjs';
|
||||
|
||||
function getTime() {
|
||||
const seconds = performance.now() * 0.001;
|
||||
return seconds;
|
||||
const seconds = performance.now() * 0.001
|
||||
return seconds
|
||||
// return Math.round(seconds * precision) / precision;
|
||||
}
|
||||
|
||||
let num_cycles_at_cps_change = 0;
|
||||
let num_ticks_since_cps_change = 0;
|
||||
let num_seconds_at_cps_change = 0;
|
||||
let cps = 0.5;
|
||||
let num_cycles_at_cps_change = 0
|
||||
let num_ticks_since_cps_change = 0
|
||||
let num_seconds_at_cps_change = 0
|
||||
let cps = 0.5
|
||||
// {id: {started: boolean}}
|
||||
const clients = new Map();
|
||||
const duration = 0.1;
|
||||
const channel = new BroadcastChannel('strudeltick');
|
||||
const clients = new Map()
|
||||
const duration = 0.1
|
||||
const channel = new BroadcastChannel('strudeltick')
|
||||
|
||||
const sendMessage = (type, payload) => {
|
||||
channel.postMessage({ type, payload });
|
||||
};
|
||||
channel.postMessage({ type, payload })
|
||||
}
|
||||
|
||||
const sendTick = (phase, duration, tick, time) => {
|
||||
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
|
||||
const tickdeadline = phase - time;
|
||||
const lastTick = time + tickdeadline;
|
||||
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps;
|
||||
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change;
|
||||
const secondsSinceLastTick = time - lastTick - duration;
|
||||
const eventLength = duration * cps;
|
||||
const end = begin + eventLength;
|
||||
const cycle = begin + secondsSinceLastTick * cps;
|
||||
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration
|
||||
const tickdeadline = phase - time
|
||||
const lastTick = time + tickdeadline
|
||||
const num_cycles_since_cps_change = num_seconds_since_cps_change * cps
|
||||
const begin = num_cycles_at_cps_change + num_cycles_since_cps_change
|
||||
const secondsSinceLastTick = time - lastTick - duration
|
||||
const eventLength = duration * cps
|
||||
const end = begin + eventLength
|
||||
const cycle = begin + secondsSinceLastTick * cps
|
||||
|
||||
sendMessage('tick', {
|
||||
begin,
|
||||
|
|
@ -39,79 +39,79 @@ const sendTick = (phase, duration, tick, time) => {
|
|||
cps,
|
||||
time,
|
||||
cycle,
|
||||
});
|
||||
num_ticks_since_cps_change++;
|
||||
};
|
||||
})
|
||||
num_ticks_since_cps_change++
|
||||
}
|
||||
|
||||
//create clock method from zyklus
|
||||
const clock = createClock(getTime, sendTick, duration);
|
||||
let started = false;
|
||||
const clock = createClock(getTime, sendTick, duration)
|
||||
let started = false
|
||||
|
||||
const startClock = (id) => {
|
||||
clients.set(id, { started: true });
|
||||
clients.set(id, { started: true })
|
||||
if (started) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
clock.start();
|
||||
started = true;
|
||||
};
|
||||
clock.start()
|
||||
started = true
|
||||
}
|
||||
const stopClock = async (id) => {
|
||||
clients.set(id, { started: false });
|
||||
clients.set(id, { started: false })
|
||||
|
||||
const otherClientStarted = Array.from(clients.values()).some((c) => c.started);
|
||||
const otherClientStarted = Array.from(clients.values()).some((c) => c.started)
|
||||
//dont stop the clock if other instances are running...
|
||||
if (!started || otherClientStarted) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
clock.stop();
|
||||
setCycle(0);
|
||||
started = false;
|
||||
};
|
||||
clock.stop()
|
||||
setCycle(0)
|
||||
started = false
|
||||
}
|
||||
|
||||
const setCycle = (cycle) => {
|
||||
num_ticks_since_cps_change = 0;
|
||||
num_cycles_at_cps_change = cycle;
|
||||
};
|
||||
num_ticks_since_cps_change = 0
|
||||
num_cycles_at_cps_change = cycle
|
||||
}
|
||||
|
||||
const processMessage = (message) => {
|
||||
const { type, payload } = message;
|
||||
const { type, payload } = message
|
||||
|
||||
switch (type) {
|
||||
case 'cpschange': {
|
||||
if (payload.cps !== cps) {
|
||||
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration;
|
||||
num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps;
|
||||
num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change;
|
||||
cps = payload.cps;
|
||||
num_ticks_since_cps_change = 0;
|
||||
const num_seconds_since_cps_change = num_ticks_since_cps_change * duration
|
||||
num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps
|
||||
num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change
|
||||
cps = payload.cps
|
||||
num_ticks_since_cps_change = 0
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
case 'setcycle': {
|
||||
setCycle(payload.cycle);
|
||||
break;
|
||||
setCycle(payload.cycle)
|
||||
break
|
||||
}
|
||||
case 'toggle': {
|
||||
if (payload.started) {
|
||||
startClock(message.id);
|
||||
startClock(message.id)
|
||||
} else {
|
||||
stopClock(message.id);
|
||||
stopClock(message.id)
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.onconnect = function (e) {
|
||||
// the incoming port
|
||||
const port = e.ports[0];
|
||||
const port = e.ports[0]
|
||||
|
||||
port.addEventListener('message', function (e) {
|
||||
processMessage(e.data);
|
||||
});
|
||||
port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
|
||||
};
|
||||
processMessage(e.data)
|
||||
})
|
||||
port.start() // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
|
||||
}
|
||||
|
||||
// used to consistently schedule events, for use in a service worker - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/clockworker.mjs>
|
||||
function createClock(
|
||||
|
|
@ -119,43 +119,43 @@ function createClock(
|
|||
callback, // called slightly before each cycle
|
||||
duration = 0.05, // duration of each cycle
|
||||
interval = 0.1, // interval between callbacks
|
||||
overlap = 0.1, // overlap between callbacks
|
||||
overlap = 0.1 // overlap between callbacks
|
||||
) {
|
||||
let tick = 0; // counts callbacks
|
||||
let phase = 0; // next callback time
|
||||
let precision = 10 ** 4; // used to round phase
|
||||
let minLatency = 0.01;
|
||||
const setDuration = (setter) => (duration = setter(duration));
|
||||
overlap = overlap || interval / 2;
|
||||
let tick = 0 // counts callbacks
|
||||
let phase = 0 // next callback time
|
||||
let precision = 10 ** 4 // used to round phase
|
||||
let minLatency = 0.01
|
||||
const setDuration = (setter) => (duration = setter(duration))
|
||||
overlap = overlap || interval / 2
|
||||
const onTick = () => {
|
||||
const t = getTime();
|
||||
const lookahead = t + interval + overlap; // the time window for this tick
|
||||
const t = getTime()
|
||||
const lookahead = t + interval + overlap // the time window for this tick
|
||||
if (phase === 0) {
|
||||
phase = t + minLatency;
|
||||
phase = t + minLatency
|
||||
}
|
||||
// callback as long as we're inside the lookahead
|
||||
while (phase < lookahead) {
|
||||
phase = Math.round(phase * precision) / precision;
|
||||
phase >= t && callback(phase, duration, tick, t);
|
||||
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
|
||||
phase += duration; // increment phase by duration
|
||||
tick++;
|
||||
phase = Math.round(phase * precision) / precision
|
||||
phase >= t && callback(phase, duration, tick, t)
|
||||
phase < t && console.log('TOO LATE', phase) // what if latency is added from outside?
|
||||
phase += duration // increment phase by duration
|
||||
tick++
|
||||
}
|
||||
};
|
||||
let intervalID;
|
||||
}
|
||||
let intervalID
|
||||
const start = () => {
|
||||
clear(); // just in case start was called more than once
|
||||
onTick();
|
||||
intervalID = setInterval(onTick, interval * 1000);
|
||||
};
|
||||
const clear = () => intervalID !== undefined && clearInterval(intervalID);
|
||||
const pause = () => clear();
|
||||
clear() // just in case start was called more than once
|
||||
onTick()
|
||||
intervalID = setInterval(onTick, interval * 1000)
|
||||
}
|
||||
const clear = () => intervalID !== undefined && clearInterval(intervalID)
|
||||
const pause = () => clear()
|
||||
const stop = () => {
|
||||
tick = 0;
|
||||
phase = 0;
|
||||
clear();
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
tick = 0
|
||||
phase = 0
|
||||
clear()
|
||||
}
|
||||
const getPhase = () => phase
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency };
|
||||
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,8 +4,8 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import createClock from './zyklus.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
import createClock from './zyklus.mjs'
|
||||
import { errorLogger, logger } from './logger.mjs'
|
||||
|
||||
export class Cyclist {
|
||||
constructor({
|
||||
|
|
@ -19,122 +19,126 @@ export class Cyclist {
|
|||
clearInterval,
|
||||
beforeStart,
|
||||
}) {
|
||||
this.started = false;
|
||||
this.beforeStart = beforeStart;
|
||||
this.cps = 0.5;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0; // query begin of last tick
|
||||
this.lastEnd = 0; // query end of last tick
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.num_cycles_at_cps_change = 0;
|
||||
this.seconds_at_cps_change; // clock phase when cps was changed
|
||||
this.onToggle = onToggle;
|
||||
this.latency = latency; // fixed trigger time offset
|
||||
this.started = false
|
||||
this.beforeStart = beforeStart
|
||||
this.cps = 0.5
|
||||
this.num_ticks_since_cps_change = 0
|
||||
this.lastTick = 0 // absolute time when last tick (clock callback) happened
|
||||
this.lastBegin = 0 // query begin of last tick
|
||||
this.lastEnd = 0 // query end of last tick
|
||||
this.getTime = getTime // get absolute time
|
||||
this.num_cycles_at_cps_change = 0
|
||||
this.seconds_at_cps_change // clock phase when cps was changed
|
||||
this.onToggle = onToggle
|
||||
this.latency = latency // fixed trigger time offset
|
||||
this.clock = createClock(
|
||||
getTime,
|
||||
// called slightly before each cycle
|
||||
(phase, duration, _, t) => {
|
||||
if (this.num_ticks_since_cps_change === 0) {
|
||||
this.num_cycles_at_cps_change = this.lastEnd;
|
||||
this.seconds_at_cps_change = phase;
|
||||
this.num_cycles_at_cps_change = this.lastEnd
|
||||
this.seconds_at_cps_change = phase
|
||||
}
|
||||
this.num_ticks_since_cps_change++;
|
||||
const seconds_since_cps_change = this.num_ticks_since_cps_change * duration;
|
||||
const num_cycles_since_cps_change = seconds_since_cps_change * this.cps;
|
||||
this.num_ticks_since_cps_change++
|
||||
const seconds_since_cps_change = this.num_ticks_since_cps_change * duration
|
||||
const num_cycles_since_cps_change = seconds_since_cps_change * this.cps
|
||||
|
||||
try {
|
||||
const begin = this.lastEnd;
|
||||
this.lastBegin = begin;
|
||||
const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change;
|
||||
this.lastEnd = end;
|
||||
this.lastTick = phase;
|
||||
const begin = this.lastEnd
|
||||
this.lastBegin = begin
|
||||
const end = this.num_cycles_at_cps_change + num_cycles_since_cps_change
|
||||
this.lastEnd = end
|
||||
this.lastTick = phase
|
||||
|
||||
if (phase < t) {
|
||||
// avoid querying haps that are in the past anyway
|
||||
console.log(`skip query: too late`);
|
||||
return;
|
||||
console.log(`skip query: too late`)
|
||||
return
|
||||
}
|
||||
|
||||
// query the pattern for events
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' });
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' })
|
||||
|
||||
haps.forEach((hap) => {
|
||||
if (hap.hasOnset()) {
|
||||
const targetTime =
|
||||
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps + this.seconds_at_cps_change + latency;
|
||||
const duration = hap.duration / this.cps;
|
||||
(hap.whole.begin - this.num_cycles_at_cps_change) / this.cps +
|
||||
this.seconds_at_cps_change +
|
||||
latency
|
||||
const duration = hap.duration / this.cps
|
||||
// the following line is dumb and only here for backwards compatibility
|
||||
// see https://codeberg.org/uzu/strudel/pulls/1004
|
||||
const deadline = targetTime - phase;
|
||||
const deadline = targetTime - phase
|
||||
// this onTrigger has another signature
|
||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime);
|
||||
onTrigger?.(hap, deadline, duration, this.cps, targetTime)
|
||||
if (hap.value.cps !== undefined && this.cps != hap.value.cps) {
|
||||
this.cps = hap.value.cps;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
this.cps = hap.value.cps
|
||||
this.num_ticks_since_cps_change = 0
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
} catch (e) {
|
||||
errorLogger(e);
|
||||
onError?.(e);
|
||||
errorLogger(e)
|
||||
onError?.(e)
|
||||
}
|
||||
},
|
||||
interval, // duration of each cycle
|
||||
0.1,
|
||||
0.1,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
);
|
||||
clearInterval
|
||||
)
|
||||
}
|
||||
now() {
|
||||
if (!this.started) {
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
||||
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration
|
||||
return this.lastBegin + secondsSinceLastTick * this.cps // + this.clock.minLatency;
|
||||
}
|
||||
setStarted(v) {
|
||||
this.started = v;
|
||||
this.onToggle?.(v);
|
||||
this.started = v
|
||||
this.onToggle?.(v)
|
||||
}
|
||||
async start() {
|
||||
await this.beforeStart?.();
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
this.num_cycles_at_cps_change = 0;
|
||||
await this.beforeStart?.()
|
||||
this.num_ticks_since_cps_change = 0
|
||||
this.num_cycles_at_cps_change = 0
|
||||
if (!this.pattern) {
|
||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||
throw new Error('Scheduler: no pattern set! call .setPattern first.')
|
||||
}
|
||||
logger('[cyclist] start');
|
||||
this.clock.start();
|
||||
this.setStarted(true);
|
||||
logger('[cyclist] start')
|
||||
this.clock.start()
|
||||
this.setStarted(true)
|
||||
}
|
||||
pause() {
|
||||
logger('[cyclist] pause');
|
||||
this.clock.pause();
|
||||
this.setStarted(false);
|
||||
logger('[cyclist] pause')
|
||||
this.clock.pause()
|
||||
this.setStarted(false)
|
||||
}
|
||||
stop() {
|
||||
logger('[cyclist] stop');
|
||||
this.clock.stop();
|
||||
this.lastEnd = 0;
|
||||
this.setStarted(false);
|
||||
logger('[cyclist] stop')
|
||||
this.clock.stop()
|
||||
this.lastEnd = 0
|
||||
this.setStarted(false)
|
||||
}
|
||||
async setPattern(pat, autostart = false) {
|
||||
this.pattern = pat;
|
||||
this.pattern = pat
|
||||
if (autostart && !this.started) {
|
||||
await this.start();
|
||||
await this.start()
|
||||
}
|
||||
}
|
||||
setCps(cps = 0.5) {
|
||||
if (this.cps === cps) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
this.cps = cps;
|
||||
this.num_ticks_since_cps_change = 0;
|
||||
this.cps = cps
|
||||
this.num_ticks_since_cps_change = 0
|
||||
}
|
||||
log(begin, end, haps) {
|
||||
const onsets = haps.filter((h) => h.hasOnset());
|
||||
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
||||
const onsets = haps.filter((h) => h.hasOnset())
|
||||
console.log(
|
||||
`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction, { gcd } from './fraction.mjs';
|
||||
import Fraction, { gcd } from './fraction.mjs'
|
||||
|
||||
/**
|
||||
* Intended for a debugging, drawLine renders the pattern as a string, where each character represents the same time span.
|
||||
|
|
@ -24,39 +24,39 @@ import Fraction, { gcd } from './fraction.mjs';
|
|||
* silence;
|
||||
*/
|
||||
function drawLine(pat, chars = 60) {
|
||||
let cycle = 0;
|
||||
let pos = Fraction(0);
|
||||
let lines = [''];
|
||||
let emptyLine = ''; // this will be the "reference" empty line, which will be copied into extra lines
|
||||
let cycle = 0
|
||||
let pos = Fraction(0)
|
||||
let lines = ['']
|
||||
let emptyLine = '' // this will be the "reference" empty line, which will be copied into extra lines
|
||||
while (lines[0].length < chars) {
|
||||
const haps = pat.queryArc(cycle, cycle + 1);
|
||||
const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration);
|
||||
const charFraction = gcd(...durations);
|
||||
const totalSlots = charFraction.inverse(); // number of character slots for the current cycle
|
||||
lines = lines.map((line) => line + '|'); // add pipe character before each cycle
|
||||
emptyLine += '|';
|
||||
const haps = pat.queryArc(cycle, cycle + 1)
|
||||
const durations = haps.filter((hap) => hap.hasOnset()).map((hap) => hap.duration)
|
||||
const charFraction = gcd(...durations)
|
||||
const totalSlots = charFraction.inverse() // number of character slots for the current cycle
|
||||
lines = lines.map((line) => line + '|') // add pipe character before each cycle
|
||||
emptyLine += '|'
|
||||
for (let i = 0; i < totalSlots; i++) {
|
||||
const [begin, end] = [pos, pos.add(charFraction)];
|
||||
const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end));
|
||||
const missingLines = matches.length - lines.length;
|
||||
const [begin, end] = [pos, pos.add(charFraction)]
|
||||
const matches = haps.filter((hap) => hap.whole.begin.lte(begin) && hap.whole.end.gte(end))
|
||||
const missingLines = matches.length - lines.length
|
||||
if (missingLines > 0) {
|
||||
lines = lines.concat(Array(missingLines).fill(emptyLine));
|
||||
lines = lines.concat(Array(missingLines).fill(emptyLine))
|
||||
}
|
||||
lines = lines.map((line, i) => {
|
||||
const hap = matches[i];
|
||||
const hap = matches[i]
|
||||
if (hap) {
|
||||
const isOnset = hap.whole.begin.eq(begin);
|
||||
const char = isOnset ? '' + hap.value : '-';
|
||||
return line + char;
|
||||
const isOnset = hap.whole.begin.eq(begin)
|
||||
const char = isOnset ? '' + hap.value : '-'
|
||||
return line + char
|
||||
}
|
||||
return line + '.';
|
||||
});
|
||||
emptyLine += '.';
|
||||
pos = pos.add(charFraction);
|
||||
return line + '.'
|
||||
})
|
||||
emptyLine += '.'
|
||||
pos = pos.add(charFraction)
|
||||
}
|
||||
cycle++;
|
||||
cycle++
|
||||
}
|
||||
return lines.join('\n');
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export default drawLine;
|
||||
export default drawLine
|
||||
|
|
|
|||
|
|
@ -10,46 +10,46 @@ https://rohandrape.net/?t=hmt
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs';
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||
import Fraction, { lcm } from './fraction.mjs';
|
||||
import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs'
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs'
|
||||
import Fraction, { lcm } from './fraction.mjs'
|
||||
|
||||
const left = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_xs, __xs] = splitAt(offs, xs);
|
||||
const [ons, offs] = n
|
||||
const [xs, ys] = x
|
||||
const [_xs, __xs] = splitAt(offs, xs)
|
||||
return [
|
||||
[offs, ons - offs],
|
||||
[zipWith((a, b) => a.concat(b), _xs, ys), __xs],
|
||||
];
|
||||
};
|
||||
]
|
||||
}
|
||||
|
||||
const right = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
const [xs, ys] = x;
|
||||
const [_ys, __ys] = splitAt(ons, ys);
|
||||
const [ons, offs] = n
|
||||
const [xs, ys] = x
|
||||
const [_ys, __ys] = splitAt(ons, ys)
|
||||
const result = [
|
||||
[ons, offs - ons],
|
||||
[zipWith((a, b) => a.concat(b), xs, _ys), __ys],
|
||||
];
|
||||
return result;
|
||||
};
|
||||
]
|
||||
return result
|
||||
}
|
||||
|
||||
const _bjork = function (n, x) {
|
||||
const [ons, offs] = n;
|
||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
||||
};
|
||||
const [ons, offs] = n
|
||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)))
|
||||
}
|
||||
|
||||
export const bjork = function (ons, steps) {
|
||||
const inverted = ons < 0;
|
||||
const absOns = Math.abs(ons);
|
||||
const offs = steps - absOns;
|
||||
const ones = Array(absOns).fill([1]);
|
||||
const zeros = Array(offs).fill([0]);
|
||||
const result = _bjork([absOns, offs], [ones, zeros]);
|
||||
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
|
||||
return inverted ? pattern.map((x) => 1 - x) : pattern;
|
||||
};
|
||||
const inverted = ons < 0
|
||||
const absOns = Math.abs(ons)
|
||||
const offs = steps - absOns
|
||||
const ones = Array(absOns).fill([1])
|
||||
const zeros = Array(offs).fill([0])
|
||||
const result = _bjork([absOns, offs], [ones, zeros])
|
||||
const pattern = flatten(result[1][0]).concat(flatten(result[1][1]))
|
||||
return inverted ? pattern.map((x) => 1 - x) : pattern
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the structure of the pattern to form an Euclidean rhythm.
|
||||
|
|
@ -128,28 +128,31 @@ export const bjork = function (ons, steps) {
|
|||
*/
|
||||
|
||||
const _euclidRot = function (pulses, steps, rotation) {
|
||||
const b = bjork(pulses, steps);
|
||||
const b = bjork(pulses, steps)
|
||||
if (rotation) {
|
||||
return rotate(b, -rotation);
|
||||
return rotate(b, -rotation)
|
||||
}
|
||||
return b;
|
||||
};
|
||||
return b
|
||||
}
|
||||
|
||||
export const euclid = register('euclid', function (pulses, steps, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, 0));
|
||||
});
|
||||
return pat.struct(_euclidRot(pulses, steps, 0))
|
||||
})
|
||||
|
||||
export const e = register('e', function (euc, pat) {
|
||||
if (!Array.isArray(euc)) {
|
||||
euc = [euc];
|
||||
euc = [euc]
|
||||
}
|
||||
const [pulses, steps = pulses, rot = 0] = euc;
|
||||
return pat.struct(_euclidRot(pulses, steps, rot));
|
||||
});
|
||||
const [pulses, steps = pulses, rot = 0] = euc
|
||||
return pat.struct(_euclidRot(pulses, steps, rot))
|
||||
})
|
||||
|
||||
export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation));
|
||||
});
|
||||
export const { euclidrot, euclidRot } = register(
|
||||
['euclidrot', 'euclidRot'],
|
||||
function (pulses, steps, rotation, pat) {
|
||||
return pat.struct(_euclidRot(pulses, steps, rotation))
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
||||
|
|
@ -166,20 +169,20 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun
|
|||
|
||||
const _euclidLegato = function (pulses, steps, rotation, pat) {
|
||||
if (pulses < 1) {
|
||||
return silence;
|
||||
return silence
|
||||
}
|
||||
const bin_pat = _euclidRot(pulses, steps, 0);
|
||||
const bin_pat = _euclidRot(pulses, steps, 0)
|
||||
const gapless = bin_pat
|
||||
.join('')
|
||||
.split('1')
|
||||
.slice(1)
|
||||
.map((s) => [s.length + 1, true]);
|
||||
return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps));
|
||||
};
|
||||
.map((s) => [s.length + 1, true])
|
||||
return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps))
|
||||
}
|
||||
|
||||
export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
|
||||
return _euclidLegato(pulses, steps, 0, pat);
|
||||
});
|
||||
return _euclidLegato(pulses, steps, 0, pat)
|
||||
})
|
||||
|
||||
/**
|
||||
* Similar to `euclid`, but each pulse is held until the next pulse,
|
||||
|
|
@ -193,9 +196,12 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
|
|||
* @example
|
||||
* note("c3").euclidLegatoRot(3,5,2)
|
||||
*/
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
export const euclidLegatoRot = register(
|
||||
['euclidLegatoRot'],
|
||||
function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* A 'euclid' variant with an additional parameter that morphs the resulting
|
||||
|
|
@ -215,7 +221,10 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s
|
|||
* sound("hh").euclidish(7,12,sine.slow(8))
|
||||
* .pan(sine.slow(8))
|
||||
*/
|
||||
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
||||
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
|
||||
return pat.struct(morphed).setSteps(steps);
|
||||
});
|
||||
export const { euclidish, eish } = register(
|
||||
['euclidish', 'eish'],
|
||||
function (pulses, steps, perc, pat) {
|
||||
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc)
|
||||
return pat.struct(morphed).setSteps(steps)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,51 +4,62 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const strudelScope = {};
|
||||
export const strudelScope = {}
|
||||
|
||||
export const evalScope = async (...args) => {
|
||||
const results = await Promise.allSettled(args);
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
|
||||
const results = await Promise.allSettled(args)
|
||||
const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value)
|
||||
results.forEach((result, i) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
|
||||
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason)
|
||||
}
|
||||
});
|
||||
})
|
||||
// Object.assign(globalThis, ...modules);
|
||||
// below is a fix for above commented out line
|
||||
// same error as https://github.com/vitest-dev/vitest/issues/1807 when running this on astro server
|
||||
modules.forEach((module) => {
|
||||
Object.entries(module).forEach(([name, value]) => {
|
||||
globalThis[name] = value;
|
||||
strudelScope[name] = value;
|
||||
});
|
||||
});
|
||||
return modules;
|
||||
};
|
||||
// globalThis[name] = value
|
||||
strudelScope[name] = value
|
||||
})
|
||||
})
|
||||
return modules
|
||||
}
|
||||
|
||||
export const addToScope = (...obj) => {
|
||||
obj.forEach((o) => {
|
||||
Object.entries(o).forEach(([name, value]) => {
|
||||
strudelScope[name] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function safeEval(str, options = {}) {
|
||||
const { wrapExpression = true, wrapAsync = true } = options;
|
||||
const { wrapExpression = true, wrapAsync = true } = options
|
||||
if (wrapExpression) {
|
||||
str = `{${str}}`;
|
||||
str = `{${str}}`
|
||||
}
|
||||
if (wrapAsync) {
|
||||
str = `(async ()=>${str})()`;
|
||||
str = `(async ()=>${str})()`
|
||||
}
|
||||
const body = `"use strict";return (${str})`;
|
||||
return Function(body)();
|
||||
const body = `"use strict";return (${str})`
|
||||
console.log(strudelScope)
|
||||
const scopedKeys = Object.keys(strudelScope)
|
||||
const scopedValues = Object.values(strudelScope)
|
||||
return Function(...scopedKeys, body)(...scopedValues)
|
||||
}
|
||||
|
||||
export const evaluate = async (code, transpiler, transpilerOptions) => {
|
||||
let meta = {};
|
||||
let meta = {}
|
||||
|
||||
if (transpiler) {
|
||||
// transform syntactically correct js code to semantically usable code
|
||||
const transpiled = transpiler(code, transpilerOptions);
|
||||
code = transpiled.output;
|
||||
meta = transpiled;
|
||||
const transpiled = transpiler(code, transpilerOptions)
|
||||
code = transpiled.output
|
||||
meta = transpiled
|
||||
}
|
||||
// if no transpiler is given, we expect a single instruction (!wrapExpression)
|
||||
const options = { wrapExpression: !!transpiler };
|
||||
let evaluated = await safeEval(code, options);
|
||||
return { mode: 'javascript', pattern: evaluated, meta };
|
||||
};
|
||||
const options = { wrapExpression: !!transpiler }
|
||||
let evaluated = await safeEval(code, options)
|
||||
return { mode: 'javascript', pattern: evaluated, meta }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,91 +4,91 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Fraction from 'fraction.js';
|
||||
import { TimeSpan } from './timespan.mjs';
|
||||
import { removeUndefineds } from './util.mjs';
|
||||
import Fraction from 'fraction.js'
|
||||
import { TimeSpan } from './timespan.mjs'
|
||||
import { removeUndefineds } from './util.mjs'
|
||||
|
||||
// Returns the start of the cycle.
|
||||
Fraction.prototype.sam = function () {
|
||||
return this.floor();
|
||||
};
|
||||
return this.floor()
|
||||
}
|
||||
|
||||
// Returns the start of the next cycle.
|
||||
Fraction.prototype.nextSam = function () {
|
||||
return this.sam().add(1);
|
||||
};
|
||||
return this.sam().add(1)
|
||||
}
|
||||
|
||||
// Returns a TimeSpan representing the begin and end of the Time value's cycle
|
||||
Fraction.prototype.wholeCycle = function () {
|
||||
return new TimeSpan(this.sam(), this.nextSam());
|
||||
};
|
||||
return new TimeSpan(this.sam(), this.nextSam())
|
||||
}
|
||||
|
||||
// The position of a time value relative to the start of its cycle.
|
||||
Fraction.prototype.cyclePos = function () {
|
||||
return this.sub(this.sam());
|
||||
};
|
||||
return this.sub(this.sam())
|
||||
}
|
||||
|
||||
Fraction.prototype.lt = function (other) {
|
||||
return this.compare(other) < 0;
|
||||
};
|
||||
return this.compare(other) < 0
|
||||
}
|
||||
|
||||
Fraction.prototype.gt = function (other) {
|
||||
return this.compare(other) > 0;
|
||||
};
|
||||
return this.compare(other) > 0
|
||||
}
|
||||
|
||||
Fraction.prototype.lte = function (other) {
|
||||
return this.compare(other) <= 0;
|
||||
};
|
||||
return this.compare(other) <= 0
|
||||
}
|
||||
|
||||
Fraction.prototype.gte = function (other) {
|
||||
return this.compare(other) >= 0;
|
||||
};
|
||||
return this.compare(other) >= 0
|
||||
}
|
||||
|
||||
Fraction.prototype.eq = function (other) {
|
||||
return this.compare(other) == 0;
|
||||
};
|
||||
return this.compare(other) == 0
|
||||
}
|
||||
|
||||
Fraction.prototype.ne = function (other) {
|
||||
return this.compare(other) != 0;
|
||||
};
|
||||
return this.compare(other) != 0
|
||||
}
|
||||
|
||||
Fraction.prototype.max = function (other) {
|
||||
return this.gt(other) ? this : other;
|
||||
};
|
||||
return this.gt(other) ? this : other
|
||||
}
|
||||
|
||||
Fraction.prototype.maximum = function (...others) {
|
||||
others = others.map((x) => new Fraction(x));
|
||||
return others.reduce((max, other) => other.max(max), this);
|
||||
};
|
||||
others = others.map((x) => new Fraction(x))
|
||||
return others.reduce((max, other) => other.max(max), this)
|
||||
}
|
||||
|
||||
Fraction.prototype.min = function (other) {
|
||||
return this.lt(other) ? this : other;
|
||||
};
|
||||
return this.lt(other) ? this : other
|
||||
}
|
||||
|
||||
Fraction.prototype.mulmaybe = function (other) {
|
||||
return other !== undefined ? this.mul(other) : undefined;
|
||||
};
|
||||
return other !== undefined ? this.mul(other) : undefined
|
||||
}
|
||||
|
||||
Fraction.prototype.divmaybe = function (other) {
|
||||
return other !== undefined ? this.div(other) : undefined;
|
||||
};
|
||||
return other !== undefined ? this.div(other) : undefined
|
||||
}
|
||||
|
||||
Fraction.prototype.addmaybe = function (other) {
|
||||
return other !== undefined ? this.add(other) : undefined;
|
||||
};
|
||||
return other !== undefined ? this.add(other) : undefined
|
||||
}
|
||||
|
||||
Fraction.prototype.submaybe = function (other) {
|
||||
return other !== undefined ? this.sub(other) : undefined;
|
||||
};
|
||||
return other !== undefined ? this.sub(other) : undefined
|
||||
}
|
||||
|
||||
Fraction.prototype.show = function (/* excludeWhole = false */) {
|
||||
// return this.toFraction(excludeWhole);
|
||||
return this.s * this.n + '/' + this.d;
|
||||
};
|
||||
return this.s * this.n + '/' + this.d
|
||||
}
|
||||
|
||||
Fraction.prototype.or = function (other) {
|
||||
return this.eq(0) ? other : this;
|
||||
};
|
||||
return this.eq(0) ? other : this
|
||||
}
|
||||
|
||||
const fraction = (n) => {
|
||||
if (typeof n === 'number') {
|
||||
|
|
@ -102,35 +102,36 @@ const fraction = (n) => {
|
|||
*/
|
||||
// n = String(n); // this is actually faster but imprecise...
|
||||
}
|
||||
return Fraction(n);
|
||||
};
|
||||
return Fraction(n)
|
||||
}
|
||||
|
||||
export const gcd = (...fractions) => {
|
||||
fractions = removeUndefineds(fractions);
|
||||
fractions = removeUndefineds(fractions)
|
||||
if (fractions.length === 0) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1));
|
||||
};
|
||||
return fractions.reduce((gcd, fraction) => gcd.gcd(fraction), fraction(1))
|
||||
}
|
||||
|
||||
export const lcm = (...fractions) => {
|
||||
fractions = removeUndefineds(fractions);
|
||||
fractions = removeUndefineds(fractions)
|
||||
if (fractions.length === 0) {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
const x = fractions.pop();
|
||||
const x = fractions.pop()
|
||||
return fractions.reduce(
|
||||
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
|
||||
x,
|
||||
);
|
||||
};
|
||||
(lcm, fraction) =>
|
||||
lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction),
|
||||
x
|
||||
)
|
||||
}
|
||||
|
||||
export const isFraction = (x) => x instanceof Fraction;
|
||||
export const isFraction = (x) => x instanceof Fraction
|
||||
|
||||
fraction._original = Fraction;
|
||||
fraction._original = Fraction
|
||||
|
||||
export default fraction;
|
||||
export default fraction
|
||||
|
||||
// "If you concern performance, cache Fraction.js objects and pass arrays/objects.“
|
||||
// -> tested memoized version, but it's slower than unmemoized, even with repeated evaluation
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ hap.mjs - <short description TODO>
|
|||
Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/hap.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import Fraction from './fraction.mjs';
|
||||
import { stringifyValues } from './util.mjs';
|
||||
import Fraction from './fraction.mjs'
|
||||
import { stringifyValues } from './util.mjs'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-this-alias */
|
||||
|
||||
export class Hap {
|
||||
/*
|
||||
|
|
@ -23,91 +25,91 @@ export class Hap {
|
|||
*/
|
||||
|
||||
constructor(whole, part, value, context = {}, stateful = false) {
|
||||
this.whole = whole;
|
||||
this.part = part;
|
||||
this.value = value;
|
||||
this.context = context;
|
||||
this.stateful = stateful;
|
||||
this.whole = whole
|
||||
this.part = part
|
||||
this.value = value
|
||||
this.context = context
|
||||
this.stateful = stateful
|
||||
if (stateful) {
|
||||
console.assert(typeof this.value === 'function', 'Stateful values must be functions');
|
||||
console.assert(typeof this.value === 'function', 'Stateful values must be functions')
|
||||
}
|
||||
}
|
||||
|
||||
get duration() {
|
||||
let duration;
|
||||
let duration
|
||||
if (typeof this.value?.duration === 'number') {
|
||||
duration = Fraction(this.value.duration);
|
||||
duration = Fraction(this.value.duration)
|
||||
} else {
|
||||
duration = this.whole.end.sub(this.whole.begin);
|
||||
duration = this.whole.end.sub(this.whole.begin)
|
||||
}
|
||||
if (typeof this.value?.clip === 'number') {
|
||||
return duration.mul(this.value.clip);
|
||||
return duration.mul(this.value.clip)
|
||||
}
|
||||
return duration;
|
||||
return duration
|
||||
}
|
||||
|
||||
get endClipped() {
|
||||
return this.whole.begin.add(this.duration);
|
||||
return this.whole.begin.add(this.duration)
|
||||
}
|
||||
|
||||
isActive(currentTime) {
|
||||
return this.whole.begin <= currentTime && this.endClipped >= currentTime;
|
||||
return this.whole.begin <= currentTime && this.endClipped >= currentTime
|
||||
}
|
||||
|
||||
isInPast(currentTime) {
|
||||
return currentTime > this.endClipped;
|
||||
return currentTime > this.endClipped
|
||||
}
|
||||
isInNearPast(margin, currentTime) {
|
||||
return currentTime - margin <= this.endClipped;
|
||||
return currentTime - margin <= this.endClipped
|
||||
}
|
||||
|
||||
isInFuture(currentTime) {
|
||||
return currentTime < this.whole.begin;
|
||||
return currentTime < this.whole.begin
|
||||
}
|
||||
isInNearFuture(margin, currentTime) {
|
||||
return currentTime < this.whole.begin && currentTime > this.whole.begin - margin;
|
||||
return currentTime < this.whole.begin && currentTime > this.whole.begin - margin
|
||||
}
|
||||
isWithinTime(min, max) {
|
||||
return this.whole.begin <= max && this.endClipped >= min;
|
||||
return this.whole.begin <= max && this.endClipped >= min
|
||||
}
|
||||
|
||||
wholeOrPart() {
|
||||
return this.whole ? this.whole : this.part;
|
||||
return this.whole ? this.whole : this.part
|
||||
}
|
||||
|
||||
withSpan(func) {
|
||||
// Returns a new hap with the function f applies to the hap timespan.
|
||||
const whole = this.whole ? func(this.whole) : undefined;
|
||||
return new Hap(whole, func(this.part), this.value, this.context);
|
||||
const whole = this.whole ? func(this.whole) : undefined
|
||||
return new Hap(whole, func(this.part), this.value, this.context)
|
||||
}
|
||||
|
||||
withValue(func) {
|
||||
// Returns a new hap with the function f applies to the hap value.
|
||||
return new Hap(this.whole, this.part, func(this.value), this.context);
|
||||
return new Hap(this.whole, this.part, func(this.value), this.context)
|
||||
}
|
||||
|
||||
hasOnset() {
|
||||
// Test whether the hap contains the onset, i.e that
|
||||
// the beginning of the part is the same as that of the whole timespan."""
|
||||
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
||||
return this.whole != undefined && this.whole.begin.equals(this.part.begin)
|
||||
}
|
||||
|
||||
hasTag(tag) {
|
||||
return this.context.tags?.includes(tag);
|
||||
return this.context.tags?.includes(tag)
|
||||
}
|
||||
|
||||
resolveState(state) {
|
||||
if (this.stateful && this.hasOnset()) {
|
||||
console.log('stateful');
|
||||
const func = this.value;
|
||||
const [newState, newValue] = func(state);
|
||||
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)];
|
||||
console.log('stateful')
|
||||
const func = this.value
|
||||
const [newState, newValue] = func(state)
|
||||
return [newState, new Hap(this.whole, this.part, newValue, this.context, false)]
|
||||
}
|
||||
return [state, this];
|
||||
return [state, this]
|
||||
}
|
||||
|
||||
spanEquals(other) {
|
||||
return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole);
|
||||
return (this.whole == undefined && other.whole == undefined) || this.whole.equals(other.whole)
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
|
|
@ -116,7 +118,7 @@ export class Hap {
|
|||
this.part.equals(other.part) &&
|
||||
// TODO would == be better ??
|
||||
this.value === other.value
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
show(compact = false) {
|
||||
|
|
@ -125,40 +127,45 @@ export class Hap {
|
|||
? compact
|
||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||
: JSON.stringify(this.value)
|
||||
: this.value;
|
||||
var spans = '';
|
||||
: this.value
|
||||
var spans = ''
|
||||
if (this.whole == undefined) {
|
||||
spans = '~' + this.part.show;
|
||||
spans = '~' + this.part.show
|
||||
} else {
|
||||
var is_whole = this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end);
|
||||
var is_whole =
|
||||
this.whole.begin.equals(this.part.begin) && this.whole.end.equals(this.part.end)
|
||||
if (!this.whole.begin.equals(this.part.begin)) {
|
||||
spans = this.whole.begin.show() + ' ⇜ ';
|
||||
spans = this.whole.begin.show() + ' ⇜ '
|
||||
}
|
||||
if (!is_whole) {
|
||||
spans += '(';
|
||||
spans += '('
|
||||
}
|
||||
spans += this.part.show();
|
||||
spans += this.part.show()
|
||||
if (!is_whole) {
|
||||
spans += ')';
|
||||
spans += ')'
|
||||
}
|
||||
if (!this.whole.end.equals(this.part.end)) {
|
||||
spans += ' ⇝ ' + this.whole.end.show();
|
||||
spans += ' ⇝ ' + this.whole.end.show()
|
||||
}
|
||||
}
|
||||
return '[ ' + spans + ' | ' + value + ' ]';
|
||||
return '[ ' + spans + ' | ' + value + ' ]'
|
||||
}
|
||||
|
||||
showWhole(compact = false) {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`;
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`
|
||||
}
|
||||
|
||||
combineContext(b) {
|
||||
const a = this;
|
||||
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
||||
const a = this
|
||||
return {
|
||||
...a.context,
|
||||
...b.context,
|
||||
locations: (a.context.locations || []).concat(b.context.locations || []),
|
||||
}
|
||||
}
|
||||
|
||||
setContext(context) {
|
||||
return new Hap(this.whole, this.part, this.value, context);
|
||||
return new Hap(this.whole, this.part, this.value, context)
|
||||
}
|
||||
|
||||
ensureObjectValue() {
|
||||
|
|
@ -169,10 +176,10 @@ export class Hap {
|
|||
if (typeof this.value !== 'object') {
|
||||
throw new Error(
|
||||
`expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`,
|
||||
'error',
|
||||
);
|
||||
'error'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Hap;
|
||||
export default Hap
|
||||
|
|
|
|||
|
|
@ -4,37 +4,29 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as controls from './controls.mjs'; // legacy
|
||||
export * from './euclid.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
import createClock from './zyklus.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
export { Fraction, controls, createClock };
|
||||
export * from './controls.mjs';
|
||||
export * from './hap.mjs';
|
||||
export * from './pattern.mjs';
|
||||
export * from './signal.mjs';
|
||||
export * from './pick.mjs';
|
||||
export * from './state.mjs';
|
||||
export * from './timespan.mjs';
|
||||
export * from './util.mjs';
|
||||
export * from './speak.mjs';
|
||||
export * from './evaluate.mjs';
|
||||
export * from './repl.mjs';
|
||||
export * from './cyclist.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './time.mjs';
|
||||
export * from './ui.mjs';
|
||||
export { default as drawLine } from './drawLine.mjs';
|
||||
import * as controls from './controls.mjs' // legacy
|
||||
export * from './euclid.mjs'
|
||||
import Fraction from './fraction.mjs'
|
||||
import createClock from './zyklus.mjs'
|
||||
import { logger } from './logger.mjs'
|
||||
export { Fraction, controls, createClock }
|
||||
export * from './controls.mjs'
|
||||
export * from './hap.mjs'
|
||||
export * from './pattern.mjs'
|
||||
export * from './signal.mjs'
|
||||
export * from './pick.mjs'
|
||||
export * from './state.mjs'
|
||||
export * from './timespan.mjs'
|
||||
export * from './util.mjs'
|
||||
export * from './speak.mjs'
|
||||
export * from './evaluate.mjs'
|
||||
export * from './repl.mjs'
|
||||
export * from './cyclist.mjs'
|
||||
export * from './logger.mjs'
|
||||
export * from './time.mjs'
|
||||
export * from './ui.mjs'
|
||||
export { default as drawLine } from './drawLine.mjs'
|
||||
// below won't work with runtime.mjs (json import fails)
|
||||
/* import * as p from './package.json';
|
||||
export const version = p.version; */
|
||||
logger('🌀 @strudel/core loaded 🌀');
|
||||
if (globalThis._strudelLoaded) {
|
||||
console.warn(
|
||||
`@strudel/core was loaded more than once...
|
||||
This might happen when you have multiple versions of strudel installed.
|
||||
Please check with "npm ls @strudel/core".`,
|
||||
);
|
||||
}
|
||||
globalThis._strudelLoaded = true;
|
||||
logger('🌀 @strudel/core loaded 🌀')
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
export const logKey = 'strudel.log';
|
||||
export const logKey = 'strudel.log'
|
||||
|
||||
let debounce = 1000,
|
||||
lastMessage,
|
||||
lastTime;
|
||||
lastTime
|
||||
|
||||
export function errorLogger(e, origin = 'cyclist') {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error(e);
|
||||
console.error(e)
|
||||
}
|
||||
logger(`[${origin}] error: ${e.message}`);
|
||||
logger(`[${origin}] error: ${e.message}`)
|
||||
}
|
||||
|
||||
export function logger(message, type, data = {}) {
|
||||
let t = performance.now();
|
||||
let t = performance.now()
|
||||
if (lastMessage === message && t - lastTime < debounce) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
lastMessage = message;
|
||||
lastTime = t;
|
||||
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
|
||||
lastMessage = message
|
||||
lastTime = t
|
||||
console.log(`%c${message}`)
|
||||
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(logKey, {
|
||||
|
|
@ -27,9 +27,9 @@ export function logger(message, type, data = {}) {
|
|||
type,
|
||||
data,
|
||||
},
|
||||
}),
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.key = logKey;
|
||||
logger.key = logKey
|
||||
|
|
|
|||
|
|
@ -4,102 +4,104 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { logger } from './logger.mjs';
|
||||
import { ClockCollator, cycleToSeconds } from './util.mjs';
|
||||
import { logger } from './logger.mjs'
|
||||
import { ClockCollator, cycleToSeconds } from './util.mjs'
|
||||
|
||||
export class NeoCyclist {
|
||||
constructor({ onTrigger, onToggle, getTime }) {
|
||||
this.started = false;
|
||||
this.cps = 0.5;
|
||||
this.getTime = getTime; // get absolute time
|
||||
this.time_at_last_tick_message = 0;
|
||||
this.started = false
|
||||
this.cps = 0.5
|
||||
this.getTime = getTime // get absolute time
|
||||
this.time_at_last_tick_message = 0
|
||||
// the clock of the worker and the audio context clock can drift apart over time
|
||||
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
|
||||
// we need to keep a rolling average of the time difference between the worker clock and audio context clock
|
||||
// in order to schedule events consistently.
|
||||
this.collator = new ClockCollator({ getTargetClockTime: getTime });
|
||||
this.onToggle = onToggle;
|
||||
this.latency = 0.1; // fixed trigger time offset
|
||||
this.cycle = 0;
|
||||
this.id = Math.round(Date.now() * Math.random());
|
||||
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
|
||||
this.worker.port.start();
|
||||
this.channel = new BroadcastChannel('strudeltick');
|
||||
this.collator = new ClockCollator({ getTargetClockTime: getTime })
|
||||
this.onToggle = onToggle
|
||||
this.latency = 0.1 // fixed trigger time offset
|
||||
this.cycle = 0
|
||||
this.id = Math.round(Date.now() * Math.random())
|
||||
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url))
|
||||
this.worker.port.start()
|
||||
this.channel = new BroadcastChannel('strudeltick')
|
||||
const tickCallback = (payload) => {
|
||||
const { cps, begin, end, cycle, time } = payload;
|
||||
this.cps = cps;
|
||||
this.cycle = cycle;
|
||||
const currentTime = this.collator.calculateOffset(time) + time;
|
||||
processHaps(begin, end, currentTime);
|
||||
this.time_at_last_tick_message = currentTime;
|
||||
};
|
||||
const { cps, begin, end, cycle, time } = payload
|
||||
this.cps = cps
|
||||
this.cycle = cycle
|
||||
const currentTime = this.collator.calculateOffset(time) + time
|
||||
processHaps(begin, end, currentTime)
|
||||
this.time_at_last_tick_message = currentTime
|
||||
}
|
||||
|
||||
const processHaps = (begin, end, currentTime) => {
|
||||
if (this.started === false) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'neocyclist' });
|
||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'neocyclist' })
|
||||
haps.forEach((hap) => {
|
||||
if (hap.hasOnset()) {
|
||||
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
||||
const targetTime = timeUntilTrigger + currentTime + this.latency;
|
||||
const duration = cycleToSeconds(hap.duration, this.cps);
|
||||
onTrigger?.(hap, 0, duration, this.cps, targetTime);
|
||||
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps)
|
||||
const targetTime = timeUntilTrigger + currentTime + this.latency
|
||||
const duration = cycleToSeconds(hap.duration, this.cps)
|
||||
onTrigger?.(hap, 0, duration, this.cps, targetTime)
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
// receive messages from worker clock and process them
|
||||
this.channel.onmessage = (message) => {
|
||||
if (!this.started) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const { payload, type } = message.data;
|
||||
const { payload, type } = message.data
|
||||
|
||||
switch (type) {
|
||||
case 'tick': {
|
||||
tickCallback(payload);
|
||||
tickCallback(payload)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
sendMessage(type, payload) {
|
||||
this.worker.port.postMessage({ type, payload, id: this.id });
|
||||
this.worker.port.postMessage({ type, payload, id: this.id })
|
||||
}
|
||||
|
||||
now() {
|
||||
const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps;
|
||||
return this.cycle + gap;
|
||||
const gap = (this.getTime() - this.time_at_last_tick_message) * this.cps
|
||||
return this.cycle + gap
|
||||
}
|
||||
setCps(cps = 1) {
|
||||
this.sendMessage('cpschange', { cps });
|
||||
this.sendMessage('cpschange', { cps })
|
||||
}
|
||||
setCycle(cycle) {
|
||||
this.sendMessage('setcycle', { cycle });
|
||||
this.sendMessage('setcycle', { cycle })
|
||||
}
|
||||
setStarted(started) {
|
||||
this.sendMessage('toggle', { started });
|
||||
this.started = started;
|
||||
this.onToggle?.(started);
|
||||
this.sendMessage('toggle', { started })
|
||||
this.started = started
|
||||
this.onToggle?.(started)
|
||||
}
|
||||
start() {
|
||||
logger('[cyclist] start');
|
||||
this.setStarted(true);
|
||||
logger('[cyclist] start')
|
||||
this.setStarted(true)
|
||||
}
|
||||
stop() {
|
||||
logger('[cyclist] stop');
|
||||
this.collator.reset();
|
||||
this.setStarted(false);
|
||||
logger('[cyclist] stop')
|
||||
this.collator.reset()
|
||||
this.setStarted(false)
|
||||
}
|
||||
setPattern(pat, autostart = false) {
|
||||
this.pattern = pat;
|
||||
this.pattern = pat
|
||||
if (autostart && !this.started) {
|
||||
this.start();
|
||||
this.start()
|
||||
}
|
||||
}
|
||||
|
||||
log(begin, end, haps) {
|
||||
const onsets = haps.filter((h) => h.hasOnset());
|
||||
console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`);
|
||||
const onsets = haps.filter((h) => h.hasOnset())
|
||||
console.log(
|
||||
`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,27 +4,27 @@ Copyright (C) 2024 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, reify, silence, register } from './pattern.mjs';
|
||||
import { Pattern, reify, silence, register } from './pattern.mjs'
|
||||
|
||||
import { _mod, clamp, objectMap } from './util.mjs';
|
||||
import { _mod, clamp, objectMap } from './util.mjs'
|
||||
|
||||
const _pick = function (lookup, pat, modulo = true) {
|
||||
const array = Array.isArray(lookup);
|
||||
const len = Object.keys(lookup).length;
|
||||
const array = Array.isArray(lookup)
|
||||
const len = Object.keys(lookup).length
|
||||
|
||||
lookup = objectMap(lookup, reify);
|
||||
lookup = objectMap(lookup, reify)
|
||||
|
||||
if (len === 0) {
|
||||
return silence;
|
||||
return silence
|
||||
}
|
||||
return pat.fmap((i) => {
|
||||
let key = i;
|
||||
let key = i
|
||||
if (array) {
|
||||
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1);
|
||||
key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1)
|
||||
}
|
||||
return lookup[key];
|
||||
});
|
||||
};
|
||||
return lookup[key]
|
||||
})
|
||||
}
|
||||
|
||||
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `inhabit`, but maintains the structure of the original patterns.
|
||||
|
|
@ -44,14 +44,14 @@ const _pick = function (lookup, pat, modulo = true) {
|
|||
export const pick = function (lookup, pat) {
|
||||
// backward compatibility - the args used to be flipped
|
||||
if (Array.isArray(pat)) {
|
||||
[pat, lookup] = [lookup, pat];
|
||||
;[pat, lookup] = [lookup, pat]
|
||||
}
|
||||
return __pick(lookup, pat);
|
||||
};
|
||||
return __pick(lookup, pat)
|
||||
}
|
||||
|
||||
const __pick = register('pick', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).innerJoin();
|
||||
});
|
||||
return _pick(lookup, pat, false).innerJoin()
|
||||
})
|
||||
|
||||
/** * The same as `pick`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -63,8 +63,8 @@ const __pick = register('pick', function (lookup, pat) {
|
|||
*/
|
||||
|
||||
export const pickmod = register('pickmod', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).innerJoin();
|
||||
});
|
||||
return _pick(lookup, pat, true).innerJoin()
|
||||
})
|
||||
|
||||
/** * pickF lets you use a pattern of numbers to pick which function to apply to another pattern.
|
||||
* @param {Pattern} pat
|
||||
|
|
@ -78,8 +78,8 @@ export const pickmod = register('pickmod', function (lookup, pat) {
|
|||
* .pickF("<0 2> 1", [jux(rev),fast(2),x=>x.lpf(800)])
|
||||
*/
|
||||
export const pickF = register('pickF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pick(lookup, funcs));
|
||||
});
|
||||
return pat.apply(pick(lookup, funcs))
|
||||
})
|
||||
|
||||
/** * The same as `pickF`, but if you pick a number greater than the size of the functions list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -89,8 +89,8 @@ export const pickF = register('pickF', function (lookup, funcs, pat) {
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
||||
return pat.apply(pickmod(lookup, funcs));
|
||||
});
|
||||
return pat.apply(pickmod(lookup, funcs))
|
||||
})
|
||||
|
||||
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
|
||||
* @param {Pattern} pat
|
||||
|
|
@ -98,8 +98,8 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickOut = register('pickOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).outerJoin();
|
||||
});
|
||||
return _pick(lookup, pat, false).outerJoin()
|
||||
})
|
||||
|
||||
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -108,8 +108,8 @@ export const pickOut = register('pickOut', function (lookup, pat) {
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).outerJoin();
|
||||
});
|
||||
return _pick(lookup, pat, true).outerJoin()
|
||||
})
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
|
|
@ -117,8 +117,8 @@ export const pickmodOut = register('pickmodOut', function (lookup, pat) {
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickRestart = register('pickRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).restartJoin();
|
||||
});
|
||||
return _pick(lookup, pat, false).restartJoin()
|
||||
})
|
||||
|
||||
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -134,8 +134,8 @@ export const pickRestart = register('pickRestart', function (lookup, pat) {
|
|||
}).scale("C:major").s("piano")
|
||||
*/
|
||||
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).restartJoin();
|
||||
});
|
||||
return _pick(lookup, pat, true).restartJoin()
|
||||
})
|
||||
|
||||
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
|
||||
* @param {Pattern} pat
|
||||
|
|
@ -143,8 +143,8 @@ export const pickmodRestart = register('pickmodRestart', function (lookup, pat)
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickReset = register('pickReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).resetJoin();
|
||||
});
|
||||
return _pick(lookup, pat, false).resetJoin()
|
||||
})
|
||||
|
||||
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -153,8 +153,8 @@ export const pickReset = register('pickReset', function (lookup, pat) {
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).resetJoin();
|
||||
});
|
||||
return _pick(lookup, pat, true).resetJoin()
|
||||
})
|
||||
|
||||
/** Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
|
||||
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
|
||||
|
|
@ -170,9 +170,12 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
|
|||
* @example
|
||||
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
|
||||
*/
|
||||
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin();
|
||||
});
|
||||
export const { inhabit, pickSqueeze } = register(
|
||||
['inhabit', 'pickSqueeze'],
|
||||
function (lookup, pat) {
|
||||
return _pick(lookup, pat, false).squeezeJoin()
|
||||
}
|
||||
)
|
||||
|
||||
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
|
||||
* it wraps around, rather than sticking at the maximum value.
|
||||
|
|
@ -185,9 +188,12 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun
|
|||
* @returns {Pattern}
|
||||
*/
|
||||
|
||||
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin();
|
||||
});
|
||||
export const { inhabitmod, pickmodSqueeze } = register(
|
||||
['inhabitmod', 'pickmodSqueeze'],
|
||||
function (lookup, pat) {
|
||||
return _pick(lookup, pat, true).squeezeJoin()
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Pick from the list of values (or patterns of values) via the index using the given
|
||||
|
|
@ -200,14 +206,14 @@ export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSq
|
|||
*/
|
||||
|
||||
export const squeeze = (pat, xs) => {
|
||||
xs = xs.map(reify);
|
||||
xs = xs.map(reify)
|
||||
if (xs.length == 0) {
|
||||
return silence;
|
||||
return silence
|
||||
}
|
||||
return pat
|
||||
.fmap((i) => {
|
||||
const key = _mod(Math.round(i), xs.length);
|
||||
return xs[key];
|
||||
const key = _mod(Math.round(i), xs.length)
|
||||
return xs[key]
|
||||
})
|
||||
.squeezeJoin();
|
||||
};
|
||||
.squeezeJoin()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { NeoCyclist } from './neocyclist.mjs';
|
||||
import { Cyclist } from './cyclist.mjs';
|
||||
import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
||||
import { NeoCyclist } from './neocyclist.mjs'
|
||||
import { Cyclist } from './cyclist.mjs'
|
||||
import { evaluate as _evaluate } from './evaluate.mjs'
|
||||
import { errorLogger, logger } from './logger.mjs'
|
||||
import { setTime } from './time.mjs'
|
||||
import { evalScope } from './evaluate.mjs'
|
||||
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'
|
||||
|
||||
export function repl({
|
||||
defaultOutput,
|
||||
|
|
@ -33,70 +33,72 @@ export function repl({
|
|||
widgets: [],
|
||||
pending: false,
|
||||
started: false,
|
||||
};
|
||||
}
|
||||
|
||||
const transpilerOptions = {
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
const updateState = (update) => {
|
||||
Object.assign(state, update);
|
||||
state.isDirty = state.code !== state.activeCode;
|
||||
state.error = state.evalError || state.schedulerError;
|
||||
onUpdateState?.(state);
|
||||
};
|
||||
Object.assign(state, update)
|
||||
state.isDirty = state.code !== state.activeCode
|
||||
state.error = state.evalError || state.schedulerError
|
||||
onUpdateState?.(state)
|
||||
}
|
||||
|
||||
const schedulerOptions = {
|
||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||
getTime,
|
||||
onToggle: (started) => {
|
||||
updateState({ started });
|
||||
onToggle?.(started);
|
||||
updateState({ started })
|
||||
onToggle?.(started)
|
||||
},
|
||||
setInterval,
|
||||
clearInterval,
|
||||
beforeStart,
|
||||
};
|
||||
}
|
||||
|
||||
// NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome
|
||||
const scheduler =
|
||||
sync && typeof SharedWorker != 'undefined' ? new NeoCyclist(schedulerOptions) : new Cyclist(schedulerOptions);
|
||||
let pPatterns = {};
|
||||
let anonymousIndex = 0;
|
||||
let allTransform;
|
||||
let eachTransform;
|
||||
sync && typeof SharedWorker != 'undefined'
|
||||
? new NeoCyclist(schedulerOptions)
|
||||
: new Cyclist(schedulerOptions)
|
||||
let pPatterns = {}
|
||||
let anonymousIndex = 0
|
||||
let allTransform
|
||||
let eachTransform
|
||||
|
||||
const hush = function () {
|
||||
pPatterns = {};
|
||||
anonymousIndex = 0;
|
||||
allTransform = undefined;
|
||||
eachTransform = undefined;
|
||||
return silence;
|
||||
};
|
||||
pPatterns = {}
|
||||
anonymousIndex = 0
|
||||
allTransform = undefined
|
||||
eachTransform = undefined
|
||||
return silence
|
||||
}
|
||||
|
||||
// helper to get a patternified pure value out
|
||||
function unpure(pat) {
|
||||
if (pat._Pattern) {
|
||||
return pat.__pure;
|
||||
return pat.__pure
|
||||
}
|
||||
return pat;
|
||||
return pat
|
||||
}
|
||||
|
||||
const setPattern = async (pattern, autostart = true) => {
|
||||
pattern = editPattern?.(pattern) || pattern;
|
||||
await scheduler.setPattern(pattern, autostart);
|
||||
return pattern;
|
||||
};
|
||||
setTime(() => scheduler.now()); // TODO: refactor?
|
||||
pattern = editPattern?.(pattern) || pattern
|
||||
await scheduler.setPattern(pattern, autostart)
|
||||
return pattern
|
||||
}
|
||||
setTime(() => scheduler.now()) // TODO: refactor?
|
||||
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
const toggle = () => scheduler.toggle();
|
||||
const stop = () => scheduler.stop()
|
||||
const start = () => scheduler.start()
|
||||
const pause = () => scheduler.pause()
|
||||
const toggle = () => scheduler.toggle()
|
||||
const setCps = (cps) => {
|
||||
scheduler.setCps(unpure(cps));
|
||||
return silence;
|
||||
};
|
||||
scheduler.setCps(unpure(cps))
|
||||
return silence
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the global tempo to the given cycles per minute
|
||||
|
|
@ -109,9 +111,9 @@ export function repl({
|
|||
* $: s("bd*4,[- sd]*2").bank('tr707')
|
||||
*/
|
||||
const setCpm = (cpm) => {
|
||||
scheduler.setCps(unpure(cpm) / 60);
|
||||
return silence;
|
||||
};
|
||||
scheduler.setCps(unpure(cpm) / 60)
|
||||
return silence
|
||||
}
|
||||
|
||||
// TODO - not documented as jsdoc examples as the test framework doesn't simulate enough context for `each` and `all`..
|
||||
|
||||
|
|
@ -128,11 +130,11 @@ export function repl({
|
|||
* all(x => x.pianoroll())
|
||||
* ```
|
||||
*/
|
||||
let allTransforms = [];
|
||||
let allTransforms = []
|
||||
const all = function (transform) {
|
||||
allTransforms.push(transform);
|
||||
return silence;
|
||||
};
|
||||
allTransforms.push(transform)
|
||||
return silence
|
||||
}
|
||||
/** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern.
|
||||
* ```
|
||||
* $: sound("bd - cp sd")
|
||||
|
|
@ -141,50 +143,50 @@ export function repl({
|
|||
* ```
|
||||
*/
|
||||
const each = function (transform) {
|
||||
eachTransform = transform;
|
||||
return silence;
|
||||
};
|
||||
eachTransform = transform
|
||||
return silence
|
||||
}
|
||||
|
||||
// set pattern methods that use this repl via closure
|
||||
const injectPatternMethods = () => {
|
||||
Pattern.prototype.p = function (id) {
|
||||
if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) {
|
||||
// allows muting a pattern x with x_ or _x
|
||||
return silence;
|
||||
return silence
|
||||
}
|
||||
if (id === '$') {
|
||||
// allows adding anonymous patterns with $:
|
||||
id = `$${anonymousIndex}`;
|
||||
anonymousIndex++;
|
||||
id = `$${anonymousIndex}`
|
||||
anonymousIndex++
|
||||
}
|
||||
pPatterns[id] = this;
|
||||
return this;
|
||||
};
|
||||
pPatterns[id] = this
|
||||
return this
|
||||
}
|
||||
Pattern.prototype.q = function (id) {
|
||||
return silence;
|
||||
};
|
||||
return silence
|
||||
}
|
||||
try {
|
||||
for (let i = 1; i < 10; ++i) {
|
||||
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
return this.p(i)
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
})
|
||||
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
||||
get() {
|
||||
return this.p(i);
|
||||
return this.p(i)
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Pattern.prototype[`q${i}`] = silence;
|
||||
})
|
||||
Pattern.prototype[`q${i}`] = silence
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('injectPatternMethods: error:', err);
|
||||
console.warn('injectPatternMethods: error:', err)
|
||||
}
|
||||
const cpm = register('cpm', function (cpm, pat) {
|
||||
return pat._fast(cpm / 60 / scheduler.cps);
|
||||
});
|
||||
return pat._fast(cpm / 60 / scheduler.cps)
|
||||
})
|
||||
return evalScope({
|
||||
all,
|
||||
each,
|
||||
|
|
@ -194,50 +196,52 @@ export function repl({
|
|||
setcps: setCps,
|
||||
setCpm,
|
||||
setcpm: setCpm,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const evaluate = async (code, autostart = true, shouldHush = true) => {
|
||||
if (!code) {
|
||||
throw new Error('no code to evaluate');
|
||||
throw new Error('no code to evaluate')
|
||||
}
|
||||
try {
|
||||
updateState({ code, pending: true });
|
||||
await injectPatternMethods();
|
||||
setTime(() => scheduler.now()); // TODO: refactor?
|
||||
await beforeEval?.({ code });
|
||||
allTransforms = []; // reset all transforms
|
||||
shouldHush && hush();
|
||||
updateState({ code, pending: true })
|
||||
await injectPatternMethods()
|
||||
setTime(() => scheduler.now()) // TODO: refactor?
|
||||
await beforeEval?.({ code })
|
||||
allTransforms = [] // reset all transforms
|
||||
shouldHush && hush()
|
||||
|
||||
if (mondo) {
|
||||
code = `mondolang\`${code}\``;
|
||||
code = `mondolang\`${code}\``
|
||||
}
|
||||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions)
|
||||
if (Object.keys(pPatterns).length) {
|
||||
let patterns = [];
|
||||
let patterns = []
|
||||
for (const [key, value] of Object.entries(pPatterns)) {
|
||||
patterns.push(value.withState((state) => state.setControls({ id: key })));
|
||||
patterns.push(value.withState((state) => state.setControls({ id: key })))
|
||||
}
|
||||
if (eachTransform) {
|
||||
// Explicit lambda so only element (not index and array) are passed
|
||||
patterns = patterns.map((x) => eachTransform(x));
|
||||
patterns = patterns.map((x) => eachTransform(x))
|
||||
}
|
||||
pattern = stack(...patterns);
|
||||
pattern = stack(...patterns)
|
||||
} else if (eachTransform) {
|
||||
pattern = eachTransform(pattern);
|
||||
pattern = eachTransform(pattern)
|
||||
}
|
||||
if (allTransforms.length) {
|
||||
for (let i in allTransforms) {
|
||||
pattern = allTransforms[i](pattern);
|
||||
pattern = allTransforms[i](pattern)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPattern(pattern)) {
|
||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||
const message = `got "${typeof evaluated}" instead of pattern`
|
||||
throw new Error(
|
||||
message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.')
|
||||
)
|
||||
}
|
||||
logger(`[eval] code updated`);
|
||||
pattern = await setPattern(pattern, autostart);
|
||||
logger(`[eval] code updated`)
|
||||
pattern = await setPattern(pattern, autostart)
|
||||
updateState({
|
||||
miniLocations: meta?.miniLocations || [],
|
||||
widgets: meta?.widgets || [],
|
||||
|
|
@ -246,18 +250,18 @@ export function repl({
|
|||
evalError: undefined,
|
||||
schedulerError: undefined,
|
||||
pending: false,
|
||||
});
|
||||
afterEval?.({ code, pattern, meta });
|
||||
return pattern;
|
||||
})
|
||||
afterEval?.({ code, pattern, meta })
|
||||
return pattern
|
||||
} catch (err) {
|
||||
logger(`[eval] error: ${err.message}`, 'error');
|
||||
console.error(err);
|
||||
updateState({ evalError: err, pending: false });
|
||||
onEvalError?.(err);
|
||||
logger(`[eval] error: ${err.message}`, 'error')
|
||||
console.error(err)
|
||||
updateState({ evalError: err, pending: false })
|
||||
onEvalError?.(err)
|
||||
}
|
||||
};
|
||||
const setCode = (code) => updateState({ code });
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
||||
}
|
||||
const setCode = (code) => updateState({ code })
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state }
|
||||
}
|
||||
|
||||
export const getTrigger =
|
||||
|
|
@ -267,13 +271,13 @@ export const getTrigger =
|
|||
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
||||
try {
|
||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||
await defaultOutput(hap, deadline, duration, cps, t);
|
||||
await defaultOutput(hap, deadline, duration, cps, t)
|
||||
}
|
||||
if (hap.context.onTrigger) {
|
||||
// call signature of output / onTrigger is different...
|
||||
await hap.context.onTrigger(hap, getTime(), cps, t);
|
||||
await hap.context.onTrigger(hap, getTime(), cps, t)
|
||||
}
|
||||
} catch (err) {
|
||||
errorLogger(err, 'getTrigger');
|
||||
errorLogger(err, 'getTrigger')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue