Compare commits
24 Commits
a711e44aea
...
06bed316b5
Author | SHA1 | Date | |
---|---|---|---|
06bed316b5 | |||
7bdc1fd674 | |||
9cbef1a1e3 | |||
7d2a2ead3f | |||
7e7a89424c | |||
aca13da8cc | |||
c6e762f96b | |||
a8eb773e6c | |||
183f02f26f | |||
be2a9499c6 | |||
5e207a1a64 | |||
7053c866f9 | |||
523ac48fb6 | |||
a1da3f9a5e | |||
f73fb0c2fb | |||
bf746a4227 | |||
f1ee6c5942 | |||
c2a60646f7 | |||
671ae6357f | |||
72ac07db5a | |||
f65b537255 | |||
1923a1d296 | |||
d2a31ce33e | |||
d0afbae699 |
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
||||
.gradle/
|
||||
build/
|
||||
.idea/
|
||||
/logs/
|
||||
logs/
|
||||
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
/deploy.ps1
|
||||
|
14
.space.kts
@ -4,15 +4,15 @@ job("Deploy") {
|
||||
}
|
||||
|
||||
container(image = "gradle:jdk17-alpine") {
|
||||
env["SPC_HOST"] = Params("spc-host")
|
||||
env["SPC_USER"] = Secrets("spc-webmaster-user")
|
||||
env["SPC_ID"] = Secrets("spc-webmaster-id")
|
||||
env["SPC_HOST"] = "{{ project:spc-host }}"
|
||||
env["SPC_USER"] = "{{ project:spc-webmaster-user }}"
|
||||
env["SPC_ID"] = "{{ project:spc-webmaster-id }}"
|
||||
kotlinScript { api ->
|
||||
api.space().projects.automation.deployments.start(
|
||||
project = api.projectIdentifier(),
|
||||
targetIdentifier = TargetIdentifier.Key("spc-site"),
|
||||
version = "current",
|
||||
// automatically update deployment status based on a status of a job
|
||||
// automatically update deployment status based on the status of the job
|
||||
syncWithAutomationJob = true
|
||||
)
|
||||
api.gradle("uploadDistribution")
|
||||
@ -26,9 +26,9 @@ job("Restart service"){
|
||||
}
|
||||
|
||||
container(image = "gradle:jdk17-alpine") {
|
||||
env["SPC_HOST"] = Params("spc-host")
|
||||
env["SPC_USER"] = Secrets("spc-webmaster-user")
|
||||
env["SPC_ID"] = Secrets("spc-webmaster-id")
|
||||
env["SPC_HOST"] = "{{ project:spc-host }}"
|
||||
env["SPC_USER"] = "{{ project:spc-webmaster-user }}"
|
||||
env["SPC_ID"] = "{{ project:spc-webmaster-id }}"
|
||||
kotlinScript { api ->
|
||||
api.gradle("reloadDistribution")
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ dependencies {
|
||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-http-redirect:$ktorVersion")
|
||||
implementation("io.ktor:ktor-server-forwarded-header:$ktorVersion")
|
||||
implementation("ch.qos.logback:logback-classic:1.2.11")
|
||||
implementation("ch.qos.logback:logback-classic:1.4.12")
|
||||
|
||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||
}
|
||||
@ -52,22 +52,23 @@ apiValidation{
|
||||
|
||||
val host = System.getenv("SPC_HOST")
|
||||
val user = System.getenv("SPC_USER")
|
||||
//val password = System.getenv("SPC_PASSWORD")
|
||||
val identityString = System.getenv("SPC_ID")
|
||||
val password = System.getenv("SPC_PASSWORD")
|
||||
val privateKey = System.getenv("SPC_ID")
|
||||
//val publicKey = System.getenv("SPC_PUBKEY")
|
||||
val serviceName = "sciprog-site"
|
||||
|
||||
if (host != null && user != null || identityString != null) {
|
||||
if (host != null && user != null || privateKey != null) {
|
||||
val uploadDistribution by tasks.creating {
|
||||
group = "distribution"
|
||||
dependsOn("installDist")
|
||||
doLast {
|
||||
JSch {
|
||||
addIdentity("spc-webmaster", identityString.encodeToByteArray(), null, null)
|
||||
addIdentity("webmaster", privateKey.encodeToByteArray(), null, null)
|
||||
}.useSession(host, user) {
|
||||
//stopping service during the upload
|
||||
execute("sudo systemctl stop $serviceName")
|
||||
uploadDirectory(buildDir.resolve("install/spc-site"), "/opt")
|
||||
//adding executable flag to the entry point
|
||||
//adding an executable flag to the entry point
|
||||
execute("sudo chmod +x /opt/spc-site/bin/spc-site")
|
||||
execute("sudo systemctl start $serviceName")
|
||||
}
|
||||
@ -78,7 +79,7 @@ if (host != null && user != null || identityString != null) {
|
||||
group = "distribution"
|
||||
doLast {
|
||||
JSch {
|
||||
addIdentity("spc-webmaster", identityString.encodeToByteArray(), null, null)
|
||||
addIdentity("webmaster", privateKey.encodeToByteArray(), null, null)
|
||||
}.useSession(host, user) {
|
||||
execute("sudo systemctl restart $serviceName")
|
||||
}
|
||||
|
@ -12,15 +12,15 @@ language: en
|
||||
<h2>Master program</h2>
|
||||
</header>
|
||||
<p>Master program "Scientific programming" at MIPT aims to prepare specialists both in application programming and domain field (such as physics, biology, biotechnology, computer science and other research areas).</p>
|
||||
<p> In modern science and technology application level programming is an integral part of any major work. And in order to be successful in this field one needs to know both software engineering and the domain field. </p>
|
||||
<p> In modern science and technology, application level programming is an integral part of any major work. And in order to be successful in this field, one needs to know both software engineering and the domain field. </p>
|
||||
<ul class="actions">
|
||||
<li><a href="${resolvePageRef("education.masters")}" class="button next">More</a></li>
|
||||
<li><a href="/education/masters" class="button next">More</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<hr/>
|
||||
|
||||
## Courses in 2022-2023:
|
||||
## Courses in 2022–2023:
|
||||
|
||||
### [Scientific literature seminar](#)
|
||||
*curated by [Aleksandr Svetlichnyi](${resolvePageRef("team")}#svetlichnyi)*
|
||||
@ -46,11 +46,6 @@ Actual program: [SPC-A-6](https://npm.mipt.ru/youtrack/articles/SPC-A-6)
|
||||
|
||||
Actual program: [SPC-A-3](https://npm.mipt.ru/youtrack/articles/SPC-A-3)
|
||||
|
||||
### [Instruments of development](https://npm.mipt.ru/youtrack/articles/SPC-A-5)
|
||||
*curated by [Alexander Nozik](${resolvePageRef("team.index")}#nozik)*
|
||||
|
||||
Actual program: [SPC-A-5](https://npm.mipt.ru/youtrack/articles/SPC-A-5)
|
||||
|
||||
### [Advanced Python](https://npm.mipt.ru/youtrack/articles/SPC-A-4)
|
||||
*by Mikhail Zelenyy*
|
||||
|
||||
|
4
data/home/content/index.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
pageName: SPC
|
||||
language: en
|
||||
---
|
@ -1,3 +1,4 @@
|
||||
|
||||
---
|
||||
type: team
|
||||
title: Varvara Kaplenko
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
**Director of the centre**
|
||||
|
||||
* PhD in particle physics.
|
||||
|
@ -4,6 +4,7 @@ title: Vladimir Palmin
|
||||
id: palmin
|
||||
order: 10
|
||||
language: en
|
||||
published: false
|
||||
image:
|
||||
path: images/people/palmin.jpg
|
||||
position: left
|
||||
|
@ -1 +1,5 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
**Vice-director for education**
|
109
data/home/content/projects/bmn.md
Normal file
@ -0,0 +1,109 @@
|
||||
---
|
||||
type: project
|
||||
title: BM@N infrastructure
|
||||
order: 150
|
||||
language: en
|
||||
image: images/projects/bmn/nica.png
|
||||
---
|
||||
## Information Systems for BM@N
|
||||
|
||||
Our team works on development of information systems and services for BM@N (Baryonic Matter at Nuclotron) experiment, part of NICA (Nuclotron-based Ion Collider fAсility) megaproject (located in Dubna, Russia). These works are performed together with scientists from JINR and other institutions.
|
||||
|
||||
The overall view of NICA complex with already running experiment BM@N and future collider experiments MPD and SPD is shown in Fig. 1. BM@N studies collisions of elementary particles and ions with a fixed target with energies up to 6 GeV per nucleon, which is a very interesting energy region for fundamental nuclear physics research.
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 1</strong>
|
||||
<img src="snark://ref/images/projects/bmn/nica.png" alt="Fig. 1"/>
|
||||
</span>
|
||||
|
||||
### Event Metadata System
|
||||
|
||||
Event metadata systems (EMS) are widely used in the physics experiments on particle collisions, where large numbers of experimental events, typically billions, are collected. At the stage of particular physics analysis of experimental data, only a small subset of collected events meeting certain criteria is usually of interest, and passing over the whole amount of stored data to form the subset is overly time-consuming and resource-intensive. Instead, metadata systems allow one to obtain only the required events (or, at least, references to them) via searching and filtering based on given fields (event attributes) recorded in the metadata system.
|
||||
|
||||
The NICA Event Metadata System has been developed for storing necessary event metadata for the NICA experiments. The information system makes it possible to quickly search for a necessary subset of physics events by required parameters to use in further event data processing. It provides summary description of collision events and their identifiers to search and select events for a desired analysis task, enables their management and convenient access; provides online and offline interfaces for selecting events of interest, such as Web and REST API services, and a dedicated C++/ROOT interface.
|
||||
|
||||
The Event Metadata System, including the Event Catalogue based on PostgreSQL, Metadata API, Web Service, and other developed components, has been provisioned using a common deployment system for the first experiment of the NICA project, BM@N. The main interfaces to EMS, namely, Web UI and REST API have been developed using Kotlin multiplatform technology as a part of the single full-stack project. The EMS provides acceptable response times for the expected amount of the event metadata. The system and its services will be further evolved and improved following operational experience.
|
||||
|
||||
Figs. 2 and 3 illustrate high-level EMS architecture and its basic Web UI view.
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 2</strong>
|
||||
<img src="snark://ref/images/projects/bmn/ems-arch.png" alt="Fig. 2"/>
|
||||
</span>
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 3</strong>
|
||||
<img src="snark://ref/images/projects/bmn/ems-web.png" alt="Fig. 3"/>
|
||||
</span>
|
||||
|
||||
### Next-Generation Event Visualization (Event Display) System
|
||||
|
||||
In high-energy physics experiments, the ability to display both detector geometry and particle tracks has become an essential feature, required for physicists to better understand particular collision events as well as to present the physical results to a wider audience.
|
||||
|
||||
For BM@N experiment, a new event visualization solution was developed based on VisionForge, a modern open-source visualization system. An important part of the solution is integration of the system with experiment's software framework BmnRoot, which is a CERN ROOT-based environment.
|
||||
|
||||
|
||||
Figs. 4, 5 illustrate how the Web interface of a new system looks like. The user can conveniently move and rotate the camera, zoom-in and zoom-out the scene as needed, browse the scene graph (object tree). For every object it is possible to change display properties such as color, opacity and visibility. Note that the full BM@N geometry model includes more than 400,000 primitives so rendering them all requires significant resources. To optimize the resource usage, so-called “prototypes” were implemented in VisionForge model for three-dimensional objects. For a single prototype its geometry is rendered only once and reused for multiple objects, which helps to significantly reduce memory usage.
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 4</strong>
|
||||
<img src="snark://ref/images/projects/bmn/vis-1.png" alt="Fig. 4"/>
|
||||
</span>
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 5</strong>
|
||||
<img src="snark://ref/images/projects/bmn/vis-2.png" alt="Fig. 5"/>
|
||||
</span>
|
||||
|
||||
### Condition Database Services
|
||||
|
||||
The Unified Condition Database (shortly called UniConDa) of the BM@N experiment is one of the advanced information systems required to support automation of experimental data collecting and processing, providing a central storage for experiment metadata being necessary for event data processing, including session and run information, detector and subsystem parameters, and descriptions of simulated event files.
|
||||
|
||||
The condition database has been earlier implemented based on the PostgreSQL database management system in accordance with a well-designed scheme. An ecosystem of its supplementary services is constantly being improved to increase overall degree of database integration and convenience of the use by collaboration members.
|
||||
|
||||
For the UniConDa ecosystem, the REST API, a modern HTTP-based interface for integration with both ROOT-based and non-ROOT software systems of the experiment, was developed.
|
||||
|
||||
Additionally, Smart Data Parser, a part of the BM@N information system based on the Unified Condition Database, was created to solve the task of the parameter data migration from accumulated files of ASCII text, CSV or XML formats to the Unified Condition Database of the BM@N experiment (Fig. 6).
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 6</strong>
|
||||
<img src="snark://ref/images/projects/bmn/sdp.png" alt="Fig. 6"/>
|
||||
</span>
|
||||
|
||||
### Monitoring Service
|
||||
|
||||
The software infrastructure of the BM@N experiment contains a set of various information systems that are essential for the work with experimental or simulated data on all processing stages, including the collection, storage, intermediate processing and physics analysis. In case one of such systems stops functioning, the work with BM@N data by collaboration members gets either impossible or, at least, much less productive. Due to this fact, the timely detection of possible failures in the systems due to software or hardware failures is fairly important.
|
||||
|
||||
The developed Monitoring Service is used to check availability and health status of information systems. This includes measuring, storing, visualizing and sending alert notifications on monitored parameters, such as CPU, memory and disk utilization, DBMS functioning parameters, response times of databases and API endpoints, ping round-trip times, and so on. The current implementation of the BM@N monitoring service is illustrated in Figs. 7, 8. Note that a related task of building highly available information services is currently a work in progress.
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 7</strong>
|
||||
<img src="snark://ref/images/projects/bmn/mon-arch.png" alt="Fig. 7"/>
|
||||
</span>
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 8</strong>
|
||||
<img src="snark://ref/images/projects/bmn/mon-db.png" alt="Fig. 8"/>
|
||||
</span>
|
||||
|
||||
|
||||
### Slow Control System Viewer
|
||||
|
||||
Web interface for slow control system of BM@N was developed. It visualizes various sensor data graphs based on parameters and time intervals provided by user as shown in Fig. 9.
|
||||
|
||||
<span class="image fit">
|
||||
<strong>Fig. 9</strong>
|
||||
<img src="snark://ref/images/projects/bmn/scs.png" alt="Fig. 9"/>
|
||||
</span>
|
||||
|
||||
### References
|
||||
|
||||
1. K. Gertsenberger, P. Klimai, M. Zelenyi. Auxiliary Services for the Condition Database of the BM@N Experiment at NICA // Phys.Part.Nucl.Lett. 20 (2023) 1217. https://www.doi.org/10.1134/S1547477123050291
|
||||
2. E. Alexandrov, I. Alexandrov, A. Chebotov, A. Degtyarev, I. Filozova, K. Gertsenberger, P. Klimai, A. Yakovlev. Implementation of the Event Metadata System for physics analysis in the NICA experiments // Journal of Physics: Conference Series 2438 (2023) 012046. https://www.doi.org/10.1088/1742-6596/2438/1/012046
|
||||
2. A. Degtyarev, K. Gertsenberger, P. Klimai. Usage of Apache Cassandra for Prototyping the Event Metadata System of the NICA Experiments // Phys.Part.Nucl.Lett. 19 (2022) 5, 562-565. https://doi.org/10.1134/S1547477122050144
|
||||
3. A. Chebotov, K. Gertsenberger, P. Klimai, A. Moshkin. Information System Based on the Condition Database for the NICA Experiments, User WEB Application, and Related Services // Phys.Part.Nucl.Lett. 19 (2022) 5, 558-561. https://doi.org/10.1134/S1547477122050132
|
||||
4. E. Alexandrov, I. Alexandrov, A. Degtyarev, K. Gertsenberger, I. Filozova, P. Klimai, A. Nozik, A. Yakovlev. Design of the Event Metadata System for the Experiments at NICA // Phys.Part.Nucl.Lett. 18 (2021) 5, 603-616. https://doi.org/10.1134/S1547477121050034
|
||||
5. K. Gertsenberger, I. Alexandrov, I. Filozova, E. Alexandrov, A. Moshkin, A. Chebotov, M. Mineev, D. Pryahina, G. Shestakova, A. Yakovlev, A. Nozik, P. Klimai. Development of Information Systems for Online and Offline Data Processing in the NICA Experiments // Phys.Part.Nucl. 52 (2021) 4, 801-807. https://doi.org/10.1134/S1063779621040250
|
||||
6. K.V. Gertsenberger, A.I. Chebotov, P.A. Klimai, I.N. Alexandrov, E.I. Alexandrov, I.A. Filozova, A.A. Moshkin. Implementation of the Condition Database for the Experiments of the NICA Complex // CEUR Workshop Proceedings, Vol. 3041 (2021) 128-132. https://doi.org/10.54546/MLIT.2021.53.54.001
|
||||
7. E.I. Alexandrov, I.N. Alexandrov, A.G. Degtyarev, I.A. Filozova, K.V. Gertsenberger, P.A. Klimai, A.V. Yakovlev. Development of the Event Metadata System for the NICA experiments // CEUR Workshop Proceedings, Vol. 3041 (2021) 439-444. https://doi.org/10.54546/MLIT.2021.80.18.001
|
||||
8. M.N.Kapishin et al, The Report on Project “Studies of Baryonic Matter at the Nuclotron (BM@N)”, https://bmn.jinr.ru/wp-content/uploads/2022/10/BMNproject_2021cor2.pdf
|
5
data/home/content/projects/bmn[info].md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
Our team works on development of information systems and services for BM@N (Baryonic Matter at Nuclotron) experiment, part of NICA (Nuclotron-based Ion Collider fAсility) megaproject (located in Dubna, Russia). These works are performed together with scientists from JINR and other institutions.
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
Controls.kt is a data acquisition framework (work in progress). It is based on DataForge, a software framework for automated data processing.
|
||||
|
||||
[Repository and documentation](https://github.com/mipt-npm/controls.kt)
|
@ -21,7 +21,7 @@ A presentation on application of (old version of) DataForge to Troitsk nu-mass a
|
||||
<iframe width="100%" height="315" src="https://www.youtube.com/embed/OpWzLXUZnLI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
## Questions and Answers
|
||||
In this section we will try to cover DataForge main ideas in the form of questions and answers.
|
||||
In this section, we will try to cover DataForge main ideas in the form of questions and answers.
|
||||
|
||||
### General
|
||||
**Q**: I have a lot of data to analyze. The analysis process is complicated, requires a lot of stages and data flow is not always obvious. To top it the data size is huge, so I don't want to perform operation I don't need (calculate something I won't need or calculate something twice). And yes, I need it to be performed in parallel and probably on remote computer. By the way, I am sick and tired of scripts that modify other scripts that control scripts. Could you help me?
|
||||
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
A metadata processing workflow manipulation framework.
|
||||
|
||||
[Repository and documentation](https://github.com/mipt-npm/dataforge-core)
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
An experimental Kotlin library for mathematical operations, built on the principle of context-oriented programming using mathematical abstractions.
|
||||
|
||||
[Repository and documentation](https://github.com/mipt-npm/kmath)
|
||||
|
@ -1 +1,5 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
Geological gas storage monitoring and leaks prevention using muons from atmosphere.
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
A kotlin visualization library wrapping popular [Plotly](https://plotly.com/javascript/) library.
|
||||
|
||||
[Repository and documentation](https://github.com/mipt-npm/plotly.kt)
|
@ -1,3 +1,7 @@
|
||||
---
|
||||
language: en
|
||||
---
|
||||
|
||||
A visualization framework written in Kotlin-multiplatform
|
||||
|
||||
[Repository and documentation](https://github.com/mipt-npm/visionforge)
|
@ -4,4 +4,4 @@ title: WIP
|
||||
language: en
|
||||
---
|
||||
|
||||
This page is work in progress.
|
||||
This page is a work in progress.
|
||||
|
BIN
data/home/images/projects/bmn/ems-arch.png
Normal file
After Width: | Height: | Size: 363 KiB |
BIN
data/home/images/projects/bmn/ems-web.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
data/home/images/projects/bmn/mon-arch.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
data/home/images/projects/bmn/mon-db.png
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
data/home/images/projects/bmn/nica.png
Normal file
After Width: | Height: | Size: 442 KiB |
BIN
data/home/images/projects/bmn/scs.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
data/home/images/projects/bmn/sdp.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
data/home/images/projects/bmn/vis-1.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
data/home/images/projects/bmn/vis-2.png
Normal file
After Width: | Height: | Size: 182 KiB |
@ -48,7 +48,7 @@
|
||||
if ($sidebar.length > 0) {
|
||||
|
||||
//adding exclusion for home link
|
||||
var $sidebar_a = $sidebar.find('a').not('.spc-home');
|
||||
let $sidebar_a = $sidebar.find('a').not('.spc-home');
|
||||
|
||||
$sidebar_a
|
||||
.addClass('scrolly')
|
||||
@ -71,7 +71,7 @@
|
||||
})
|
||||
.each(function () {
|
||||
|
||||
var $this = $(this),
|
||||
let $this = $(this),
|
||||
id = $this.attr('href'),
|
||||
$section = $(id);
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
|
||||
// Scrollex.
|
||||
$section.scrollex({
|
||||
mode: 'middle',
|
||||
// mode: 'middle',
|
||||
top: '-20vh',
|
||||
bottom: '-20vh',
|
||||
initialize: function () {
|
||||
@ -133,7 +133,7 @@
|
||||
// Spotlights.
|
||||
$('.spotlights > section')
|
||||
.scrollex({
|
||||
mode: 'middle',
|
||||
// mode: 'middle',
|
||||
top: '-10vh',
|
||||
bottom: '-10vh',
|
||||
initialize: function () {
|
||||
@ -151,7 +151,7 @@
|
||||
})
|
||||
.each(function () {
|
||||
|
||||
var $this = $(this),
|
||||
let $this = $(this),
|
||||
$image = $this.find('.image'),
|
||||
$img = $image.find('img'),
|
||||
x;
|
||||
@ -171,7 +171,7 @@
|
||||
// Features.
|
||||
$('.features')
|
||||
.scrollex({
|
||||
mode: 'middle',
|
||||
// mode: 'middle',
|
||||
top: '-20vh',
|
||||
bottom: '-20vh',
|
||||
initialize: function () {
|
||||
@ -187,20 +187,22 @@
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
||||
//From https://www.w3schools.com/howto/howto_js_collapsible.asp
|
||||
const coll = document.getElementsByClassName("collapsible");
|
||||
let i;
|
||||
|
||||
for (i = 0; i < coll.length; i++) {
|
||||
coll[i].addEventListener("click", function() {
|
||||
this.classList.toggle("active");
|
||||
const content = this.nextElementSibling;
|
||||
if (content.style.maxHeight){
|
||||
//From https://www.w3schools.com/howto/howto_js_collapsible.asp
|
||||
let collapsibles = document.getElementsByClassName("collapsible");
|
||||
|
||||
Array.from(collapsibles).forEach(item => {
|
||||
item.addEventListener("click", function () {
|
||||
this.classList.toggle("collapsible-expanded");
|
||||
let target = item.attributes.getNamedItem("data-target").value;
|
||||
let content = document.getElementById(target);
|
||||
if (content.style.maxHeight) {
|
||||
content.style.maxHeight = null;
|
||||
} else {
|
||||
content.style.maxHeight = content.scrollHeight + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
@ -1,587 +1,587 @@
|
||||
(function($) {
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Generate an indented list of links from a nav. Meant for use with panel().
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.navList = function() {
|
||||
/**
|
||||
* Generate an indented list of links from a nav. Meant for use with panel().
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.navList = function () {
|
||||
|
||||
var $this = $(this);
|
||||
$a = $this.find('a'),
|
||||
b = [];
|
||||
const $this = $(this);
|
||||
$a = $this.find('a'),
|
||||
b = [];
|
||||
|
||||
$a.each(function() {
|
||||
$a.each(function () {
|
||||
|
||||
var $this = $(this),
|
||||
indent = Math.max(0, $this.parents('li').length - 1),
|
||||
href = $this.attr('href'),
|
||||
target = $this.attr('target');
|
||||
const $this = $(this),
|
||||
indent = Math.max(0, $this.parents('li').length - 1),
|
||||
href = $this.attr('href'),
|
||||
target = $this.attr('target');
|
||||
|
||||
b.push(
|
||||
'<a ' +
|
||||
'class="link depth-' + indent + '"' +
|
||||
( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
|
||||
( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
|
||||
'>' +
|
||||
'<span class="indent-' + indent + '"></span>' +
|
||||
$this.text() +
|
||||
'</a>'
|
||||
);
|
||||
b.push(
|
||||
'<a ' +
|
||||
'class="link depth-' + indent + '"' +
|
||||
((typeof target !== 'undefined' && target !== '') ? ' target="' + target + '"' : '') +
|
||||
((typeof href !== 'undefined' && href !== '') ? ' href="' + href + '"' : '') +
|
||||
'>' +
|
||||
'<span class="indent-' + indent + '"></span>' +
|
||||
$this.text() +
|
||||
'</a>'
|
||||
);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
return b.join('');
|
||||
return b.join('');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Panel-ify an element.
|
||||
* @param {object} userConfig User config.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.panel = function(userConfig) {
|
||||
/**
|
||||
* Panel-ify an element.
|
||||
* @param {object} userConfig User config.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.panel = function (userConfig) {
|
||||
|
||||
// No elements?
|
||||
if (this.length == 0)
|
||||
return $this;
|
||||
let $this = $(this);
|
||||
// No elements?
|
||||
if (this.length === 0)
|
||||
return $this;
|
||||
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
|
||||
for (var i=0; i < this.length; i++)
|
||||
$(this[i]).panel(userConfig);
|
||||
for (let i = 0; i < this.length; i++)
|
||||
$(this[i]).panel(userConfig);
|
||||
|
||||
return $this;
|
||||
return $this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Vars.
|
||||
var $this = $(this),
|
||||
$body = $('body'),
|
||||
$window = $(window),
|
||||
id = $this.attr('id'),
|
||||
config;
|
||||
// Vars.
|
||||
let $body = $('body'),
|
||||
$window = $(window),
|
||||
id = $this.attr('id'),
|
||||
config;
|
||||
|
||||
// Config.
|
||||
config = $.extend({
|
||||
// Config.
|
||||
config = $.extend({
|
||||
|
||||
// Delay.
|
||||
delay: 0,
|
||||
// Delay.
|
||||
delay: 0,
|
||||
|
||||
// Hide panel on link click.
|
||||
hideOnClick: false,
|
||||
// Hide panel on link click.
|
||||
hideOnClick: false,
|
||||
|
||||
// Hide panel on escape keypress.
|
||||
hideOnEscape: false,
|
||||
// Hide panel on escape keypress.
|
||||
hideOnEscape: false,
|
||||
|
||||
// Hide panel on swipe.
|
||||
hideOnSwipe: false,
|
||||
// Hide panel on swipe.
|
||||
hideOnSwipe: false,
|
||||
|
||||
// Reset scroll position on hide.
|
||||
resetScroll: false,
|
||||
// Reset scroll position on hide.
|
||||
resetScroll: false,
|
||||
|
||||
// Reset forms on hide.
|
||||
resetForms: false,
|
||||
// Reset forms on hide.
|
||||
resetForms: false,
|
||||
|
||||
// Side of viewport the panel will appear.
|
||||
side: null,
|
||||
// Side of viewport the panel will appear.
|
||||
side: null,
|
||||
|
||||
// Target element for "class".
|
||||
target: $this,
|
||||
// Target element for "class".
|
||||
target: $this,
|
||||
|
||||
// Class to toggle.
|
||||
visibleClass: 'visible'
|
||||
// Class to toggle.
|
||||
visibleClass: 'visible'
|
||||
|
||||
}, userConfig);
|
||||
}, userConfig);
|
||||
|
||||
// Expand "target" if it's not a jQuery object already.
|
||||
if (typeof config.target != 'jQuery')
|
||||
config.target = $(config.target);
|
||||
// Expand "target" if it's not a jQuery object already.
|
||||
if (typeof config.target != 'jQuery')
|
||||
config.target = $(config.target);
|
||||
|
||||
// Panel.
|
||||
// Panel.
|
||||
|
||||
// Methods.
|
||||
$this._hide = function(event) {
|
||||
// Methods.
|
||||
$this._hide = function (event) {
|
||||
|
||||
// Already hidden? Bail.
|
||||
if (!config.target.hasClass(config.visibleClass))
|
||||
return;
|
||||
// Already hidden? Bail.
|
||||
if (!config.target.hasClass(config.visibleClass))
|
||||
return;
|
||||
|
||||
// If an event was provided, cancel it.
|
||||
if (event) {
|
||||
// If an event was provided, cancel it.
|
||||
if (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Hide.
|
||||
config.target.removeClass(config.visibleClass);
|
||||
// Hide.
|
||||
config.target.removeClass(config.visibleClass);
|
||||
|
||||
// Post-hide stuff.
|
||||
window.setTimeout(function() {
|
||||
// Post-hide stuff.
|
||||
window.setTimeout(function () {
|
||||
|
||||
// Reset scroll position.
|
||||
if (config.resetScroll)
|
||||
$this.scrollTop(0);
|
||||
// Reset scroll position.
|
||||
if (config.resetScroll)
|
||||
$this.scrollTop(0);
|
||||
|
||||
// Reset forms.
|
||||
if (config.resetForms)
|
||||
$this.find('form').each(function() {
|
||||
this.reset();
|
||||
});
|
||||
// Reset forms.
|
||||
if (config.resetForms)
|
||||
$this.find('form').each(function () {
|
||||
this.reset();
|
||||
});
|
||||
|
||||
}, config.delay);
|
||||
}, config.delay);
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
// Vendor fixes.
|
||||
$this
|
||||
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
|
||||
.css('-webkit-overflow-scrolling', 'touch');
|
||||
// Vendor fixes.
|
||||
$this
|
||||
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
|
||||
.css('-webkit-overflow-scrolling', 'touch');
|
||||
|
||||
// Hide on click.
|
||||
if (config.hideOnClick) {
|
||||
// Hide on click.
|
||||
if (config.hideOnClick) {
|
||||
|
||||
$this.find('a')
|
||||
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
|
||||
$this.find('a')
|
||||
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
|
||||
|
||||
$this
|
||||
.on('click', 'a', function(event) {
|
||||
$this
|
||||
.on('click', 'a', function (event) {
|
||||
|
||||
var $a = $(this),
|
||||
href = $a.attr('href'),
|
||||
target = $a.attr('target');
|
||||
const $a = $(this),
|
||||
href = $a.attr('href'),
|
||||
target = $a.attr('target');
|
||||
|
||||
if (!href || href == '#' || href == '' || href == '#' + id)
|
||||
return;
|
||||
if (!href || href === '#' || href === '' || href === '#' + id)
|
||||
return;
|
||||
|
||||
// Cancel original event.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Cancel original event.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Hide panel.
|
||||
$this._hide();
|
||||
// Hide panel.
|
||||
$this._hide();
|
||||
|
||||
// Redirect to href.
|
||||
window.setTimeout(function() {
|
||||
// Redirect to href.
|
||||
window.setTimeout(function () {
|
||||
|
||||
if (target == '_blank')
|
||||
window.open(href);
|
||||
else
|
||||
window.location.href = href;
|
||||
if (target === '_blank')
|
||||
window.open(href);
|
||||
else
|
||||
window.location.href = href;
|
||||
|
||||
}, config.delay + 10);
|
||||
}, config.delay + 10);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Event: Touch stuff.
|
||||
$this.on('touchstart', function(event) {
|
||||
// Event: Touch stuff.
|
||||
$this.on('touchstart', function (event) {
|
||||
|
||||
$this.touchPosX = event.originalEvent.touches[0].pageX;
|
||||
$this.touchPosY = event.originalEvent.touches[0].pageY;
|
||||
$this.touchPosX = event.originalEvent.touches[0].pageX;
|
||||
$this.touchPosY = event.originalEvent.touches[0].pageY;
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
$this.on('touchmove', function(event) {
|
||||
$this.on('touchmove', function (event) {
|
||||
|
||||
if ($this.touchPosX === null
|
||||
|| $this.touchPosY === null)
|
||||
return;
|
||||
if ($this.touchPosX === null
|
||||
|| $this.touchPosY === null)
|
||||
return;
|
||||
|
||||
var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
|
||||
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
|
||||
th = $this.outerHeight(),
|
||||
ts = ($this.get(0).scrollHeight - $this.scrollTop());
|
||||
const diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
|
||||
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
|
||||
th = $this.outerHeight(),
|
||||
ts = ($this.get(0).scrollHeight - $this.scrollTop());
|
||||
|
||||
// Hide on swipe?
|
||||
if (config.hideOnSwipe) {
|
||||
// Hide on swipe?
|
||||
if (config.hideOnSwipe) {
|
||||
|
||||
var result = false,
|
||||
boundary = 20,
|
||||
delta = 50;
|
||||
let result = false;
|
||||
const boundary = 20,
|
||||
delta = 50;
|
||||
|
||||
switch (config.side) {
|
||||
switch (config.side) {
|
||||
|
||||
case 'left':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
|
||||
break;
|
||||
case 'left':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
|
||||
break;
|
||||
case 'right':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
|
||||
break;
|
||||
|
||||
case 'top':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
|
||||
break;
|
||||
case 'top':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
|
||||
break;
|
||||
case 'bottom':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
if (result) {
|
||||
|
||||
$this.touchPosX = null;
|
||||
$this.touchPosY = null;
|
||||
$this._hide();
|
||||
$this.touchPosX = null;
|
||||
$this.touchPosY = null;
|
||||
$this._hide();
|
||||
|
||||
return false;
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent vertical scrolling past the top or bottom.
|
||||
if (($this.scrollTop() < 0 && diffY < 0)
|
||||
|| (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
|
||||
// Prevent vertical scrolling past the top or bottom.
|
||||
if (($this.scrollTop() < 0 && diffY < 0)
|
||||
|| (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Event: Prevent certain events inside the panel from bubbling.
|
||||
$this.on('click touchend touchstart touchmove', function(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
// Event: Prevent certain events inside the panel from bubbling.
|
||||
$this.on('click touchend touchstart touchmove', function (event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
// Event: Hide panel if a child anchor tag pointing to its ID is clicked.
|
||||
$this.on('click', 'a[href="#' + id + '"]', function(event) {
|
||||
// Event: Hide panel if a child anchor tag pointing to its ID is clicked.
|
||||
$this.on('click', 'a[href="#' + id + '"]', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
config.target.removeClass(config.visibleClass);
|
||||
config.target.removeClass(config.visibleClass);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Body.
|
||||
// Body.
|
||||
|
||||
// Event: Hide panel on body click/tap.
|
||||
$body.on('click touchend', function(event) {
|
||||
$this._hide(event);
|
||||
});
|
||||
// Event: Hide panel on body click/tap.
|
||||
$body.on('click touchend', function (event) {
|
||||
$this._hide(event);
|
||||
});
|
||||
|
||||
// Event: Toggle.
|
||||
$body.on('click', 'a[href="#' + id + '"]', function(event) {
|
||||
// Event: Toggle.
|
||||
$body.on('click', 'a[href="#' + id + '"]', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
config.target.toggleClass(config.visibleClass);
|
||||
config.target.toggleClass(config.visibleClass);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Window.
|
||||
// Window.
|
||||
|
||||
// Event: Hide on ESC.
|
||||
if (config.hideOnEscape)
|
||||
$window.on('keydown', function(event) {
|
||||
// Event: Hide on ESC.
|
||||
if (config.hideOnEscape)
|
||||
$window.on('keydown', function (event) {
|
||||
|
||||
if (event.keyCode == 27)
|
||||
$this._hide(event);
|
||||
if (event.keyCode === 27)
|
||||
$this._hide(event);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
return $this;
|
||||
return $this;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply "placeholder" attribute polyfill to one or more forms.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.placeholder = function() {
|
||||
/**
|
||||
* Apply "placeholder" attribute polyfill to one or more forms.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.placeholder = function () {
|
||||
|
||||
// Browser natively supports placeholders? Bail.
|
||||
if (typeof (document.createElement('input')).placeholder != 'undefined')
|
||||
return $(this);
|
||||
// Browser natively supports placeholders? Bail.
|
||||
if (typeof (document.createElement('input')).placeholder != 'undefined')
|
||||
return $(this);
|
||||
|
||||
// No elements?
|
||||
if (this.length == 0)
|
||||
return $this;
|
||||
// No elements?
|
||||
if (this.length === 0)
|
||||
return $this;
|
||||
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
|
||||
for (var i=0; i < this.length; i++)
|
||||
$(this[i]).placeholder();
|
||||
for (let i = 0; i < this.length; i++)
|
||||
$(this[i]).placeholder();
|
||||
|
||||
return $this;
|
||||
return $this;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Vars.
|
||||
var $this = $(this);
|
||||
// Vars.
|
||||
var $this = $(this);
|
||||
|
||||
// Text, TextArea.
|
||||
$this.find('input[type=text],textarea')
|
||||
.each(function() {
|
||||
// Text, TextArea.
|
||||
$this.find('input[type=text],textarea')
|
||||
.each(function () {
|
||||
|
||||
var i = $(this);
|
||||
const i = $(this);
|
||||
|
||||
if (i.val() == ''
|
||||
|| i.val() == i.attr('placeholder'))
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
if (i.val() === ''
|
||||
|| i.val() === i.attr('placeholder'))
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
|
||||
})
|
||||
.on('blur', function() {
|
||||
})
|
||||
.on('blur', function () {
|
||||
|
||||
var i = $(this);
|
||||
const i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
|
||||
if (i.val() == '')
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
if (i.val() === '')
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
|
||||
})
|
||||
.on('focus', function() {
|
||||
})
|
||||
.on('focus', function () {
|
||||
|
||||
var i = $(this);
|
||||
const i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
|
||||
if (i.val() == i.attr('placeholder'))
|
||||
i
|
||||
.removeClass('polyfill-placeholder')
|
||||
.val('');
|
||||
if (i.val() === i.attr('placeholder'))
|
||||
i
|
||||
.removeClass('polyfill-placeholder')
|
||||
.val('');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Password.
|
||||
$this.find('input[type=password]')
|
||||
.each(function() {
|
||||
// Password.
|
||||
$this.find('input[type=password]')
|
||||
.each(function () {
|
||||
|
||||
var i = $(this);
|
||||
var x = $(
|
||||
$('<div>')
|
||||
.append(i.clone())
|
||||
.remove()
|
||||
.html()
|
||||
.replace(/type="password"/i, 'type="text"')
|
||||
.replace(/type=password/i, 'type=text')
|
||||
);
|
||||
const i = $(this);
|
||||
const x = $(
|
||||
$('<div>')
|
||||
.append(i.clone())
|
||||
.remove()
|
||||
.html()
|
||||
.replace(/type="password"/i, 'type="text"')
|
||||
.replace(/type=password/i, 'type=text')
|
||||
);
|
||||
|
||||
if (i.attr('id') != '')
|
||||
x.attr('id', i.attr('id') + '-polyfill-field');
|
||||
if (i.attr('id') !== '')
|
||||
x.attr('id', i.attr('id') + '-polyfill-field');
|
||||
|
||||
if (i.attr('name') != '')
|
||||
x.attr('name', i.attr('name') + '-polyfill-field');
|
||||
if (i.attr('name') !== '')
|
||||
x.attr('name', i.attr('name') + '-polyfill-field');
|
||||
|
||||
x.addClass('polyfill-placeholder')
|
||||
.val(x.attr('placeholder')).insertAfter(i);
|
||||
x.addClass('polyfill-placeholder')
|
||||
.val(x.attr('placeholder')).insertAfter(i);
|
||||
|
||||
if (i.val() == '')
|
||||
i.hide();
|
||||
else
|
||||
x.hide();
|
||||
if (i.val() === '')
|
||||
i.hide();
|
||||
else
|
||||
x.hide();
|
||||
|
||||
i
|
||||
.on('blur', function(event) {
|
||||
i
|
||||
.on('blur', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
const x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
|
||||
if (i.val() == '') {
|
||||
if (i.val() === '') {
|
||||
|
||||
i.hide();
|
||||
x.show();
|
||||
i.hide();
|
||||
x.show();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
x
|
||||
.on('focus', function(event) {
|
||||
x
|
||||
.on('focus', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
|
||||
const i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
|
||||
|
||||
x.hide();
|
||||
x.hide();
|
||||
|
||||
i
|
||||
.show()
|
||||
.focus();
|
||||
i
|
||||
.show()
|
||||
.focus();
|
||||
|
||||
})
|
||||
.on('keypress', function(event) {
|
||||
})
|
||||
.on('keypress', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
x.val('');
|
||||
event.preventDefault();
|
||||
x.val('');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// Events.
|
||||
$this
|
||||
.on('submit', function() {
|
||||
// Events.
|
||||
$this
|
||||
.on('submit', function () {
|
||||
|
||||
$this.find('input[type=text],input[type=password],textarea')
|
||||
.each(function(event) {
|
||||
$this.find('input[type=text],input[type=password],textarea')
|
||||
.each(function (event) {
|
||||
|
||||
var i = $(this);
|
||||
const i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
i.attr('name', '');
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
i.attr('name', '');
|
||||
|
||||
if (i.val() == i.attr('placeholder')) {
|
||||
if (i.val() === i.attr('placeholder')) {
|
||||
|
||||
i.removeClass('polyfill-placeholder');
|
||||
i.val('');
|
||||
i.removeClass('polyfill-placeholder');
|
||||
i.val('');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
.on('reset', function(event) {
|
||||
})
|
||||
.on('reset', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.preventDefault();
|
||||
|
||||
$this.find('select')
|
||||
.val($('option:first').val());
|
||||
$this.find('select')
|
||||
.val($('option:first').val());
|
||||
|
||||
$this.find('input,textarea')
|
||||
.each(function() {
|
||||
$this.find('input,textarea')
|
||||
.each(function () {
|
||||
|
||||
var i = $(this),
|
||||
x;
|
||||
const i = $(this);
|
||||
let x;
|
||||
|
||||
i.removeClass('polyfill-placeholder');
|
||||
i.removeClass('polyfill-placeholder');
|
||||
|
||||
switch (this.type) {
|
||||
switch (this.type) {
|
||||
|
||||
case 'submit':
|
||||
case 'reset':
|
||||
break;
|
||||
case 'submit':
|
||||
case 'reset':
|
||||
break;
|
||||
|
||||
case 'password':
|
||||
i.val(i.attr('defaultValue'));
|
||||
case 'password':
|
||||
i.val(i.attr('defaultValue'));
|
||||
|
||||
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
|
||||
if (i.val() == '') {
|
||||
i.hide();
|
||||
x.show();
|
||||
}
|
||||
else {
|
||||
i.show();
|
||||
x.hide();
|
||||
}
|
||||
if (i.val() === '') {
|
||||
i.hide();
|
||||
x.show();
|
||||
} else {
|
||||
i.show();
|
||||
x.hide();
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
i.attr('checked', i.attr('defaultValue'));
|
||||
break;
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
i.attr('checked', i.attr('defaultValue'));
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
i.val(i.attr('defaultValue'));
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
i.val(i.attr('defaultValue'));
|
||||
|
||||
if (i.val() == '') {
|
||||
i.addClass('polyfill-placeholder');
|
||||
i.val(i.attr('placeholder'));
|
||||
}
|
||||
if (i.val() === '') {
|
||||
i.addClass('polyfill-placeholder');
|
||||
i.val(i.attr('placeholder'));
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
i.val(i.attr('defaultValue'));
|
||||
break;
|
||||
default:
|
||||
i.val(i.attr('defaultValue'));
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
return $this;
|
||||
return $this;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves elements to/from the first positions of their respective parents.
|
||||
* @param {jQuery} $elements Elements (or selector) to move.
|
||||
* @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
|
||||
*/
|
||||
$.prioritize = function($elements, condition) {
|
||||
/**
|
||||
* Moves elements to/from the first positions of their respective parents.
|
||||
* @param {jQuery} $elements Elements (or selector) to move.
|
||||
* @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
|
||||
*/
|
||||
$.prioritize = function ($elements, condition) {
|
||||
|
||||
var key = '__prioritize';
|
||||
const key = '__prioritize';
|
||||
|
||||
// Expand $elements if it's not already a jQuery object.
|
||||
if (typeof $elements != 'jQuery')
|
||||
$elements = $($elements);
|
||||
// Expand $elements if it's not already a jQuery object.
|
||||
if (typeof $elements != 'jQuery')
|
||||
$elements = $($elements);
|
||||
|
||||
// Step through elements.
|
||||
$elements.each(function() {
|
||||
// Step through elements.
|
||||
$elements.each(function () {
|
||||
|
||||
var $e = $(this), $p,
|
||||
$parent = $e.parent();
|
||||
const $e = $(this);
|
||||
let $p;
|
||||
const $parent = $e.parent();
|
||||
|
||||
// No parent? Bail.
|
||||
if ($parent.length == 0)
|
||||
return;
|
||||
// No parent? Bail.
|
||||
if ($parent.length === 0)
|
||||
return;
|
||||
|
||||
// Not moved? Move it.
|
||||
if (!$e.data(key)) {
|
||||
// Not moved? Move it.
|
||||
if (!$e.data(key)) {
|
||||
|
||||
// Condition is false? Bail.
|
||||
if (!condition)
|
||||
return;
|
||||
// Condition is false? Bail.
|
||||
if (!condition)
|
||||
return;
|
||||
|
||||
// Get placeholder (which will serve as our point of reference for when this element needs to move back).
|
||||
$p = $e.prev();
|
||||
// Get placeholder (which will serve as our point of reference for when this element needs to move back).
|
||||
$p = $e.prev();
|
||||
|
||||
// Couldn't find anything? Means this element's already at the top, so bail.
|
||||
if ($p.length == 0)
|
||||
return;
|
||||
// Couldn't find anything? Means this element's already at the top, so bail.
|
||||
if ($p.length === 0)
|
||||
return;
|
||||
|
||||
// Move element to top of parent.
|
||||
$e.prependTo($parent);
|
||||
// Move element to top of parent.
|
||||
$e.prependTo($parent);
|
||||
|
||||
// Mark element as moved.
|
||||
$e.data(key, $p);
|
||||
// Mark element as moved.
|
||||
$e.data(key, $p);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Moved already?
|
||||
else {
|
||||
// Moved already?
|
||||
else {
|
||||
|
||||
// Condition is true? Bail.
|
||||
if (condition)
|
||||
return;
|
||||
// Condition is true? Bail.
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
$p = $e.data(key);
|
||||
$p = $e.data(key);
|
||||
|
||||
// Move element back to its original location (using our placeholder).
|
||||
$e.insertAfter($p);
|
||||
// Move element back to its original location (using our placeholder).
|
||||
$e.insertAfter($p);
|
||||
|
||||
// Unmark element as moved.
|
||||
$e.removeData(key);
|
||||
// Unmark element as moved.
|
||||
$e.removeData(key);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
})(jQuery);
|
@ -5,7 +5,7 @@ section_title: Как поступить?
|
||||
language: ru
|
||||
---
|
||||
Чтобы принять участие в программе, необходимо:
|
||||
* заполнить **[анкету](https://forms.yandex.ru/cloud/641ad7fe73cee702c7753776/)**. В анкете надо указать одного или нескольких научных руководителей, с которыми вы бы хотели работать;
|
||||
* заполнить **[анкету](https://forms.yandex.ru/cloud/660a6e8690fa7b0435e99a28/)**. В анкете надо указать одного или нескольких научных руководителей, с которыми вы бы хотели работать;
|
||||
* **до 1 июня (I поток) или до 15 июля (II поток)** пройти собеседование с научными руководителями и согласовать предполагаемый план обучения;
|
||||
* подать документы в магистратуру МФТИ согласно [правилам поступления](https://pk.mipt.ru/master/).
|
||||
|
||||
@ -13,7 +13,7 @@ language: ru
|
||||
**ВАЖНО:** предварительное согласование с научным руководителем является **обязательным** для обучения в нашей магистратуре.
|
||||
|
||||
|
||||
Сроки приёма документов при поступлении на очные программы магистратуры в МФТИ в 2023–2024 учебном году:
|
||||
Сроки приёма документов при поступлении на очные программы магистратуры в МФТИ в 2024–2025 учебном году:
|
||||
* I поток поступления: 10 апреля – 20 июля;
|
||||
* II поток поступления: 21 июля – 4 августа.
|
||||
|
||||
|
@ -5,8 +5,7 @@ section_title: О программе
|
||||
language: ru
|
||||
---
|
||||
|
||||
<iframe width="100%" height="315" src="https://www.youtube.com/embed/pniPs5ne294" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
[Видео презентация программы](https://youtu.be/pniPs5ne294?si=TzY9yvt6PmYcDIq7)
|
||||
|
||||
Магистерская программа МФТИ **"Научное программное обеспечение"** создана при поддержке двух школ МФТИ: Физтех-школы физики и исследований им. Ландау ([ЛФИ](https://mipt.ru/education/departments/lpr/)) и Физтех-школы прикладной математики и информатики ([ФПМИ](https://mipt.ru/education/departments/fpmi/)), а также ряда академических и промышленных партнеров. В ее основе лежит взаимодействие студента и [научного руководителя](#mentors).
|
||||
|
||||
|
27
data/magprog/content/mentors/Andreev.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
content_type: magprog_mentor
|
||||
name: Михаил Андреев
|
||||
id: Andreev
|
||||
image: images/mentors/Andreev.jpg
|
||||
language: ru
|
||||
---
|
||||
#### Организация
|
||||
Сбербанк КИБ
|
||||
|
||||
Департамент Глобальных Рынков
|
||||
|
||||
#### Биография
|
||||
Образование.
|
||||
МФТИ, факультет проблем физики и энергетики (2001).
|
||||
Кандидат технических наук (2004), аспирантура Института Космических Исследований РАН, отдел технологий спутникового мониторинга.
|
||||
|
||||
Большой опыт работы в промышленной разработке программных систем. С 2013 года в инвестиционных банках для задач торговли и управления рисками внебиржевых производных финансовых инструментов.
|
||||
|
||||
Читаю на ФПМИ курс по выбору «Математические модели и численные методы в финансах» -- вводный курс по расчетам производных финансовых инструментов для 4ого курса бакалавриата.
|
||||
|
||||
#### Направление исследований
|
||||
Производные финансовые инструменты, вычислительные финансы.
|
||||
|
||||
#### Требования к студентам
|
||||
* Владение C++ и Python
|
||||
* Базовые теоретические знания в области количественных финансов
|
5
data/magprog/content/mentors/Andreev[info].md
Normal file
@ -0,0 +1,5 @@
|
||||
**Сбербанк КИБ. Департамент Глобальных Рынков.**
|
||||
|
||||
Моделирование производных финансовые инструментов, вычислительные финансы.
|
||||
|
||||
Ключевые слова: *Вычислительные финансы, C++, Python.*
|
@ -3,8 +3,8 @@ content_type: magprog_mentor
|
||||
name: Андрей Александрович Ефанов
|
||||
id: Efanov
|
||||
image: images/mentors/Efanov.jpg
|
||||
published: false
|
||||
language: ru
|
||||
published: false
|
||||
---
|
||||
|
||||
#### Организация
|
||||
|
35
data/magprog/content/mentors/Gorbachev.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
content_type: magprog_mentor
|
||||
name: Алексей Юрьевич Горбачев
|
||||
id: Gorbachev
|
||||
image: images/mentors/Gorbachev.jpg
|
||||
language: ru
|
||||
---
|
||||
|
||||
#### Организация
|
||||
Заведующий лабораторией протеомного анализа ФГБУ ФНКЦ ФХМ им. Ю.М. Лопухина ФМБА России.
|
||||
|
||||
#### Биография
|
||||
* Победитель Всероссийской олимпиады по биологии (Диплом III степени, 2004).
|
||||
* Обучение в МГУ им. М.В. Ломоносова, биологический факультет, кафедра молекулярной биологии (2004-2009).
|
||||
* Обучение в аспирантуре ФГБУ ФНКЦ ФХМ ФМБА России по специальности Биохимия (2009-2012).
|
||||
* Защита кандидатской диссертации (2014).
|
||||
* Руководство проектом "Разработка системы генетического скрининга
|
||||
сердечно-сосудистых заболеваний" в рамках программы трансляционных исследований и инноваций Сколковского Института Науки и Технологий при сотрудничестве с РЖД (2015-2016).
|
||||
* Коммерческая деятельность в рамках собственной научно-исследовательской компании "Институт геномного анализа", создание проекта Zenome (2016-н.в.)
|
||||
* Научная деятельность в ФГБУ ФНКЦ ФХМ им. Ю.М. Лопухина ФМБА России в должности заведующего лабораторий протеомного анализа (2022-н.в.)
|
||||
|
||||
#### Направление исследований
|
||||
|
||||
* Изучение механизмов долговременной эпигенетической памяти у адгезивно-инвазивных форм кишечной палочки (E. coli), ассоциированной с болезнью Крона.
|
||||
* Исследование репертуаров T-клеточных рецепторов у пациентов с болезнью Крона.
|
||||
* Протеогеномное профилирование образцов рака молочной железы как основа для разработки платформы прототипирования персонализированных панелей для тераностики.
|
||||
* Разработка новых методов диагностики рака с помощью секвенирования свободной циркулирующей ДНК плазмы крови.
|
||||
|
||||
#### Требования к студентам
|
||||
|
||||
* Знание языков программирования: Python, R, bash.
|
||||
* Умение работать с Git.
|
||||
* Английский язык на уровне чтения технической документации.
|
||||
* Знание ОС Linux и Windows на уровне опытного пользователя.
|
||||
* Знания биохимии и молекулярной биологии на уровне университетской программы.
|
3
data/magprog/content/mentors/Gorbachev[info].md
Normal file
@ -0,0 +1,3 @@
|
||||
**Заведующий лабораторией протеомного анализа ФГБУ ФНКЦ ФХМ им. Ю.М. Лопухина ФМБА России.**
|
||||
|
||||
Ключевые слова: *Био-информатика, протеомный анализ, диагностика рака*.
|
20
data/magprog/content/mentors/Kulikov.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
content_type: magprog_mentor
|
||||
name: Александр Владимирович Куликов
|
||||
id: Kulikov
|
||||
image: images/mentors/Kulikov.jpg
|
||||
language: ru
|
||||
---
|
||||
|
||||
* Руководитель проекта Департамента инцидентов и рисков АНО "Аналитический центр при Правительстве РФ".
|
||||
* Доцент кафедры высшей математики МФТИ
|
||||
|
||||
#### Биография
|
||||
Закончил в 2006 году механико-математический факультет МГУ, в 2009 защитил кандидатскую диссертацию по специальности 01.01.05 - теория вероятностей и математическая статистика по теме "Многомерные когерентные меры риска и их применение к решению задач финансовой математики". Доцент кафедры высшей математики, также преподает различные курсы по финансовой математике в физтех-школе ФПМИ. Сфера научных интересов - финансовая математика, оценка риска, ценообразование производных финансовых инструментов. Занимался риск-менеджментом в ООО "Газпром экспорт", ПАО "НК "Роснефть". На данный момент руководитель проекта Департамента инцидентов и рисков АНО "Аналитический центр при Правительстве РФ"
|
||||
|
||||
#### Направление исследований
|
||||
|
||||
Финансовая математика, оценка риска, ценообразование производных финансовых инструментов.
|
||||
|
||||
#### Требования к студентам
|
||||
Хорошие знания теории вероятностей, математической статистики и случайных процессов, желательны базовые знания в финансовой математике.
|
5
data/magprog/content/mentors/Kulikov[info].md
Normal file
@ -0,0 +1,5 @@
|
||||
**Руководитель проекта Департамента инцидентов и рисков АНО "Аналитический центр при Правительстве РФ"**
|
||||
|
||||
Финансовая математика, оценка риска, ценообразование производных финансовых инструментов.
|
||||
|
||||
Ключевые слова: *математические инструменты, финансы, оценка риска*
|
@ -4,6 +4,7 @@ name: Владимир Пальмин
|
||||
id: Palmin
|
||||
image: images/mentors/Palmin.jpg
|
||||
language: ru
|
||||
published: false
|
||||
---
|
||||
|
||||
#### Организация
|
||||
|
@ -4,6 +4,7 @@ name: Айно Константиновна Скасырская
|
||||
id: Skasyrskaya
|
||||
image: images/mentors/Skasyrskaya.jpg
|
||||
language: ru
|
||||
published: false
|
||||
---
|
||||
|
||||
#### Организация
|
||||
|
19
data/magprog/content/mentors/Zharnikov.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
content_type: magprog_mentor
|
||||
name: Тимур Жарников
|
||||
id: Zharnikov
|
||||
image: images/mentors/Zharnikov.jpg
|
||||
language: ru
|
||||
---
|
||||
|
||||
Ведущий научный сотрудник в области геофизических методов исследования скважин и распределенных оптоволоконных измерений.
|
||||
|
||||
#### Биография
|
||||
Выпускник Физтеха. Тимур Жарников имеет более чем 20-летний опыт работы в области акустического каротажа, оптического зондирования и полимерной физики. Начал свою карьеру в Российской академии наук в области полимерной физики. Затем он работал с Schlumberger в качестве старшего научного сотрудника в течение более чем 10 лет по акустическому каротажу и распределенным измерениям волоконной оптики. Затем работал в других компаниях в качестве научного специалиста.
|
||||
|
||||
#### Направление исследований
|
||||
|
||||
Разработка эффективных методов анализа и интерпретации данных геофизических измерений (акустика и распределенные оптоволоконные измерения). Разработка научно-прикладного ПО.
|
||||
|
||||
#### Требования к студентам
|
||||
Хорошая подготовка по физике и математике.
|
5
data/magprog/content/mentors/Zharnikov[info].md
Normal file
@ -0,0 +1,5 @@
|
||||
**Ведущий научный сотрудник в области геофизических методов исследования скважин и распределенных оптоволоконных измерений**
|
||||
|
||||
Разработка эффективных методов анализа и интерпретации данных геофизических измерений (акустика и распределенные оптоволоконные измерения). Разработка научно-прикладного ПО.
|
||||
|
||||
Ключевые слова: *научное ПО, вычислительная физика, акустика, прикладные исследования*
|
@ -6,13 +6,9 @@ image: images/mentors/kluev.jpg
|
||||
language: ru
|
||||
---
|
||||
|
||||
#### Организация
|
||||
|
||||
КПМ РИТМ
|
||||
|
||||
#### Биография
|
||||
|
||||
Учился и защищался в БелГУ, с 2022 года работаю в КПМ РИТМ лидом команды программирующих математиков.
|
||||
Учился и защищался в БелГУ, с 2022 года работаю в Julia ORG лидом команды программирующих математиков.
|
||||
|
||||
#### Направления исследований
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
**Лидер команды программирующих математиков КПМ РИТМ**
|
||||
**Лидер команды программирующих математиков на Julia**
|
||||
|
||||
Разработка и исследования методов компьютерного моделирования гибридных динамических систем.
|
||||
|
||||
|
@ -6,18 +6,14 @@ image: images/mentors/komisov.jpg
|
||||
language: ru
|
||||
---
|
||||
|
||||
#### Организация
|
||||
|
||||
КПМ РИТМ
|
||||
|
||||
#### Биография
|
||||
|
||||
2019 - к.ф.-м.н.
|
||||
* 2019 - к.ф.-м.н.
|
||||
Московский государственный университет им. М.В. Ломоносова, Москва
|
||||
2012 - Магистр
|
||||
* 2012 - Магистр
|
||||
НИУ "БелГУ"
|
||||
Инженерно физический, Кафедра теоретической и математической физики, Прикладные математика и физика
|
||||
2010 - Бакалавр
|
||||
* 2010 - Бакалавр
|
||||
НИУ БелГУ
|
||||
Инженерно физический, Кафедра теоретической и математической физики, Прикладные математика и физика
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
**CTO КПМ РИТМ**
|
||||
**Разработчик на Julia**
|
||||
|
||||
Разработка и исследования методов компьютерного моделирования гибридных динамических систем.
|
||||
|
||||
|
@ -4,6 +4,7 @@ name: Ирина Геннадьевна Низовцева
|
||||
id: Nizovtseva
|
||||
image: images/mentors/Nizovtseva.jpg
|
||||
language: ru
|
||||
published: false
|
||||
---
|
||||
#### Биография
|
||||
* К.ф.-м.н. с 2009г (направление 01.04.14)
|
||||
|
BIN
data/magprog/images/mentors/Andreev.jpg
Normal file
After Width: | Height: | Size: 253 KiB |
BIN
data/magprog/images/mentors/Gorbachev.jpg
Normal file
After Width: | Height: | Size: 197 KiB |
BIN
data/magprog/images/mentors/Kulikov.jpg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
data/magprog/images/mentors/Zharnikov.jpg
Normal file
After Width: | Height: | Size: 250 KiB |
@ -1,4 +1,4 @@
|
||||
kotlin.code.style=official
|
||||
|
||||
toolsVersion=0.14.9-kotlin-1.8.20
|
||||
toolsVersion=0.15.2-kotlin-1.9.22
|
||||
snarkVersion=0.1.0-dev-1
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,7 +1,6 @@
|
||||
rootProject.name = "spc-site"
|
||||
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
//enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
|
@ -4,63 +4,22 @@ import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.application.log
|
||||
import io.ktor.server.config.tryGetString
|
||||
import io.ktor.server.plugins.forwardedheaders.ForwardedHeaders
|
||||
import io.ktor.server.plugins.forwardedheaders.XForwardedHeaders
|
||||
import io.ktor.server.response.respondRedirect
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.routing
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.snark.html.SiteBuilder
|
||||
import space.kscience.snark.html.SnarkHtmlPlugin
|
||||
import space.kscience.snark.html.readDirectory
|
||||
import space.kscience.snark.ktor.prepareSnarkDataCacheDirectory
|
||||
import space.kscience.dataforge.data.forEach
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.workspace.FileData
|
||||
import space.kscience.dataforge.workspace.directory
|
||||
import space.kscience.snark.html.SnarkHtml
|
||||
import space.kscience.snark.html.readSiteData
|
||||
import space.kscience.snark.ktor.site
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.relativeTo
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
|
||||
private fun Application.copyResource(resource: String, dataDirectory: Path) {
|
||||
val uri = javaClass.getResource("/$resource")?.toURI()
|
||||
?: error("Resource $resource not found")
|
||||
val targetPath = dataDirectory.resolve(resource)
|
||||
|
||||
if (Files.isDirectory(targetPath)) {
|
||||
log.info("Using existing data directory at $targetPath.")
|
||||
} else {
|
||||
log.info("Copying data from $uri into $targetPath.")
|
||||
targetPath.createDirectories()
|
||||
//Copy everything into a temporary directory
|
||||
|
||||
fun copyFromPath(rootPath: Path) {
|
||||
Files.walk(rootPath).forEach { source: Path ->
|
||||
if (source.isRegularFile()) {
|
||||
val relative = source.relativeTo(rootPath).toString()
|
||||
val destination: Path = targetPath.resolve(relative)
|
||||
destination.parent.createDirectories()
|
||||
Files.copy(source, destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("jar" == uri.scheme) {
|
||||
FileSystems.newFileSystem(uri, emptyMap<String, Any>()).use { fs ->
|
||||
val rootPath: Path = fs.provider().getPath(uri)
|
||||
copyFromPath(rootPath)
|
||||
}
|
||||
} else {
|
||||
val rootPath = uri.toPath()
|
||||
copyFromPath(rootPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.exists
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
@ -69,26 +28,33 @@ fun Application.spcModule() {
|
||||
install(ForwardedHeaders)
|
||||
install(XForwardedHeaders)
|
||||
|
||||
val snark = Global.request(SnarkHtmlPlugin)
|
||||
|
||||
val dataDirectory = Path.of(
|
||||
environment.config.tryGetString("ktor.environment.dataDirectory") ?: "data"
|
||||
)
|
||||
|
||||
if (!prepareSnarkDataCacheDirectory(dataDirectory)) {
|
||||
copyResource("common", dataDirectory)
|
||||
copyResource("home", dataDirectory)
|
||||
copyResource("magprog", dataDirectory)
|
||||
val context = Context {
|
||||
plugin(SnarkHtml)
|
||||
}
|
||||
|
||||
val siteData: DataTree<Any> = snark.readDirectory(dataDirectory)
|
||||
val snark = context.request(SnarkHtml)
|
||||
|
||||
site(snark, siteData, block = SiteBuilder::spcSite)
|
||||
val dataDirectoryString = environment.config.propertyOrNull("snark.dataDirectory")?.getString() ?: "data"
|
||||
|
||||
val dataDirectory = Path(dataDirectoryString)
|
||||
|
||||
if(!dataDirectory.exists()){
|
||||
error("Data directory at $dataDirectory is not resolved")
|
||||
}
|
||||
|
||||
val siteData = snark.readSiteData(context) {
|
||||
directory(snark.io, Name.EMPTY, dataDirectory)
|
||||
}
|
||||
|
||||
siteData.forEach { namedData ->
|
||||
log.debug("Loading data {} from {}", namedData.name, namedData.meta[FileData.FILE_PATH_KEY])
|
||||
}
|
||||
|
||||
routing {
|
||||
get("magprog") {
|
||||
call.respondRedirect("education/masters")
|
||||
}
|
||||
site(context, siteData, content = spcSite)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package center.sciprog
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.data.Data
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.await
|
||||
import space.kscience.dataforge.data.getByType
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
@ -11,7 +10,6 @@ import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.getIndexed
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.snark.html.*
|
||||
|
||||
|
||||
@ -22,58 +20,55 @@ private val Data<*>.fragment: String
|
||||
get() = meta["fragment"].string ?: ""
|
||||
|
||||
|
||||
internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsName()) {
|
||||
internal val bmk = HtmlSite {
|
||||
|
||||
// val data: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
|
||||
static("assets")
|
||||
static("images")
|
||||
static("common.assets.webfonts", "assets/webfonts")
|
||||
static("common", "")
|
||||
|
||||
site(prefix, data) {
|
||||
static("assets")
|
||||
static("images")
|
||||
static("common.assets.webfonts", "assets/webfonts")
|
||||
static("common", "")
|
||||
val about: Data<PageFragment> = siteData.resolveHtml("about")
|
||||
val team: Data<PageFragment> = siteData.resolveHtml("team.index")
|
||||
val teamData: Map<Name, Data<PageFragment>> = siteData.resolveAllHtml { _, meta -> meta["type"].string == "team" }
|
||||
val solutions: Data<PageFragment> = siteData.resolveHtml("lotSeis")
|
||||
val partners: Data<PageFragment> = siteData.resolveHtml("partners")
|
||||
val partnersData = runBlocking { siteData.getByType<Meta>("partnersData")!!.await() }
|
||||
|
||||
val about: Data<HtmlFragment> = data.resolveHtml("about")
|
||||
val team: Data<HtmlFragment> = data.resolveHtml("team.index")
|
||||
val teamData: Map<Name, Data<HtmlFragment>> = data.resolveAllHtml { _, meta -> meta["type"].string == "team" }
|
||||
val solutions: Data<HtmlFragment> = data.resolveHtml("lotSeis")
|
||||
val partners: Data<HtmlFragment> = data.resolveHtml("partners")
|
||||
val partnersData = runBlocking { data.getByType<Meta>("partnersData")!!.await() }
|
||||
|
||||
page {
|
||||
head {
|
||||
title = "БМК-Сервис"
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
}
|
||||
meta {
|
||||
name = "viewport"
|
||||
content = "width=device-width, initial-scale=1, user-scalable=no"
|
||||
}
|
||||
page {
|
||||
head {
|
||||
title = "БМК-Сервис"
|
||||
meta {
|
||||
charset = "utf-8"
|
||||
}
|
||||
meta {
|
||||
name = "viewport"
|
||||
content = "width=device-width, initial-scale=1, user-scalable=no"
|
||||
}
|
||||
link {
|
||||
rel = "stylesheet"
|
||||
href = resolveRef("assets/css/main.css")
|
||||
}
|
||||
noScript {
|
||||
link {
|
||||
rel = "stylesheet"
|
||||
href = resolveRef("assets/css/main.css")
|
||||
}
|
||||
noScript {
|
||||
link {
|
||||
rel = "stylesheet"
|
||||
href = resolveRef("assets/css/noscript.css")
|
||||
}
|
||||
href = resolveRef("assets/css/noscript.css")
|
||||
}
|
||||
}
|
||||
body("is-preload") {
|
||||
}
|
||||
body("is-preload") {
|
||||
// Wrapper
|
||||
div {
|
||||
id = "wrapper"
|
||||
div {
|
||||
id = "wrapper"
|
||||
// Header
|
||||
header("alt") {
|
||||
id = "header"
|
||||
span("logo") {
|
||||
img {
|
||||
src = "images/logo.svg"
|
||||
alt = ""
|
||||
}
|
||||
header("alt") {
|
||||
id = "header"
|
||||
span("logo") {
|
||||
img {
|
||||
src = "images/logo.svg"
|
||||
alt = ""
|
||||
}
|
||||
h1 { +"""БМК-Сервис""" }
|
||||
}
|
||||
h1 { +"""БМК-Сервис""" }
|
||||
// p {
|
||||
// +"""Just another free, fully responsive site template"""
|
||||
// br {
|
||||
@ -90,99 +85,98 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
|
||||
// }
|
||||
// +"""."""
|
||||
// }
|
||||
}
|
||||
}
|
||||
// Nav
|
||||
nav {
|
||||
id = "nav"
|
||||
ul {
|
||||
li {
|
||||
a(classes = "active") {
|
||||
href = "#${about.fragment}"
|
||||
+about.title
|
||||
nav {
|
||||
id = "nav"
|
||||
ul {
|
||||
li {
|
||||
a(classes = "active") {
|
||||
href = "#${about.fragment}"
|
||||
+about.title
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${team.fragment}"
|
||||
+team.title
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${solutions.fragment}"
|
||||
+solutions.title
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${partners.fragment}"
|
||||
+partners.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
id = "main"
|
||||
section("main") {
|
||||
id = about.fragment
|
||||
div("spotlight") {
|
||||
div("content") {
|
||||
header("major") {
|
||||
h2 { +about.title }
|
||||
}
|
||||
fragment(about)
|
||||
}
|
||||
}
|
||||
}
|
||||
section("main") {
|
||||
id = team.fragment
|
||||
header("major") {
|
||||
h2 { +team.title }
|
||||
}
|
||||
fragment(team)
|
||||
teamData.values.sortedBy { it.order }.forEach { data ->
|
||||
span("image left") {
|
||||
img {
|
||||
src = resolveRef("images/${data.meta["image"].string!!}")
|
||||
height = "120dp"
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${team.fragment}"
|
||||
+team.title
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${solutions.fragment}"
|
||||
+solutions.title
|
||||
}
|
||||
}
|
||||
li {
|
||||
a {
|
||||
href = "#${partners.fragment}"
|
||||
+partners.title
|
||||
h3 { +data.title }
|
||||
fragment(data)
|
||||
}
|
||||
}
|
||||
section("main") {
|
||||
id = solutions.fragment
|
||||
header("major") {
|
||||
h2 { +solutions.title }
|
||||
fragment(solutions)
|
||||
span("image fit") {
|
||||
img {
|
||||
src = resolveRef("images/fresnel_lands_critdepth2.png")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
id = "main"
|
||||
section("main") {
|
||||
id = about.fragment
|
||||
div("spotlight") {
|
||||
div("content") {
|
||||
header("major") {
|
||||
h2 { +about.title }
|
||||
}
|
||||
htmlData(about)
|
||||
}
|
||||
}
|
||||
}
|
||||
section("main") {
|
||||
id = team.fragment
|
||||
header("major") {
|
||||
h2 { +team.title }
|
||||
}
|
||||
htmlData(team)
|
||||
teamData.values.sortedBy { it.order }.forEach { data ->
|
||||
span("image left") {
|
||||
img {
|
||||
src = resolveRef("images/${data.meta["image"].string!!}")
|
||||
height = "120dp"
|
||||
}
|
||||
}
|
||||
h3 { +data.title }
|
||||
htmlData(data)
|
||||
}
|
||||
}
|
||||
section("main") {
|
||||
id = solutions.fragment
|
||||
header("major") {
|
||||
h2 { +solutions.title }
|
||||
htmlData(solutions)
|
||||
span("image fit") {
|
||||
img {
|
||||
src = resolveRef("images/fresnel_lands_critdepth2.png")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
section("main") {
|
||||
id = partners.fragment
|
||||
header("major") {
|
||||
h2 { +partners.title }
|
||||
htmlData(partners)
|
||||
table {
|
||||
partnersData.getIndexed("content").values.forEach {
|
||||
tr {
|
||||
td {
|
||||
span("image right") {
|
||||
img {
|
||||
src = resolveRef(it["image"].string!!)
|
||||
height = "120dp"
|
||||
width = "auto"
|
||||
}
|
||||
section("main") {
|
||||
id = partners.fragment
|
||||
header("major") {
|
||||
h2 { +partners.title }
|
||||
fragment(partners)
|
||||
table {
|
||||
partnersData.getIndexed("content").values.forEach {
|
||||
tr {
|
||||
td {
|
||||
span("image right") {
|
||||
img {
|
||||
src = resolveRef(it["image"].string!!)
|
||||
height = "120dp"
|
||||
width = "auto"
|
||||
}
|
||||
h3 {
|
||||
a(href = it["target"].string!!) {
|
||||
+it["title"].string!!
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
a(href = it["target"].string!!) {
|
||||
+it["title"].string!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,8 +185,9 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
footer {
|
||||
footer {
|
||||
// id = "footer"
|
||||
// section {
|
||||
// h2 { +"""Aliquam sed mauris""" }
|
||||
@ -254,38 +249,37 @@ internal fun SiteBuilder.bmk(data: DataTree<Any>, prefix: Name = "bmk".parseAsNa
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
p("copyright") {
|
||||
+"""SPC. Design:"""
|
||||
a {
|
||||
href = "https://html5up.net"
|
||||
+"""HTML5 UP"""
|
||||
}
|
||||
+"""."""
|
||||
p("copyright") {
|
||||
+"""SPC. Design:"""
|
||||
a {
|
||||
href = "https://html5up.net"
|
||||
+"""HTML5 UP"""
|
||||
}
|
||||
+"""."""
|
||||
}
|
||||
}
|
||||
}
|
||||
// Scripts
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.scrollex.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.scrolly.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/browser.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/breakpoints.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/util.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/main.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.scrollex.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.scrolly.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/browser.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/breakpoints.min.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/util.js")
|
||||
}
|
||||
script {
|
||||
src = resolveRef("assets/js/main.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,19 @@ import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.int
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.replaceLast
|
||||
import space.kscience.snark.html.*
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
|
||||
context(WebPage) private fun FlowContent.spcSpotlightContent(
|
||||
landing: HtmlData,
|
||||
content: Map<Name, HtmlData>,
|
||||
|
||||
context(PageContextWithData, FlowContent) private fun spcSpotlightContent(
|
||||
landing: Data<PageFragment>,
|
||||
content: Map<Name, Data<PageFragment>>,
|
||||
) {
|
||||
// Banner
|
||||
// Note: The "styleN" class below should match that of the header element.
|
||||
@ -32,7 +36,7 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
|
||||
h1 { +(landing.meta["title"].string ?: "???") }
|
||||
}
|
||||
div("content") {
|
||||
htmlData(landing)
|
||||
fragment(landing)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,8 +67,9 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
|
||||
header("major") {
|
||||
h3 { +(entry.meta["title"].string ?: "???") }
|
||||
}
|
||||
val infoData = data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") }) ?: entry
|
||||
htmlData(infoData)
|
||||
val infoData =
|
||||
data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") }) ?: entry
|
||||
fragment(infoData)
|
||||
ul("actions") {
|
||||
li {
|
||||
a(classes = "button") {
|
||||
@ -82,16 +87,13 @@ context(WebPage) private fun FlowContent.spcSpotlightContent(
|
||||
}
|
||||
|
||||
|
||||
internal fun SiteBuilder.spcSpotlight(
|
||||
internal fun SiteContextWithData.spcSpotlight(
|
||||
address: String,
|
||||
contentFilter: (Name, Meta) -> Boolean,
|
||||
) {
|
||||
val pageName = address.parseAsName()
|
||||
val languagePrefix = languagePrefix
|
||||
val body = data.resolveHtmlOrNull(languagePrefix + pageName)
|
||||
?: data.resolveHtmlOrNull(pageName) ?: error("Could not find body for $pageName")
|
||||
val content: Map<Name, Data<HtmlFragment>> =
|
||||
data.resolveAllHtml { name, meta -> name.startsWith(languagePrefix) && contentFilter(name, meta) }
|
||||
val body = siteData.resolveHtmlOrNull(pageName) ?: error("Could not find body for $pageName")
|
||||
val content: Map<Name, Data<PageFragment>> = siteData.resolveAllHtml { name, meta -> contentFilter(name, meta) }
|
||||
|
||||
val meta = body.meta
|
||||
page(pageName) {
|
||||
@ -107,10 +109,6 @@ internal fun SiteBuilder.spcSpotlight(
|
||||
}
|
||||
|
||||
content.forEach { (name, contentBody) ->
|
||||
page(name, contentBody.meta) {
|
||||
spcPageContent {
|
||||
htmlData(contentBody)
|
||||
}
|
||||
}
|
||||
page(name, contentBody.meta, spcPage(contentBody))
|
||||
}
|
||||
}
|
@ -2,19 +2,17 @@ package center.sciprog
|
||||
|
||||
import html5up.forty.fortyScripts
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.data.Data
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.snark.html.*
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
|
||||
context(WebPage) internal fun HTML.spcPageContent(
|
||||
fragment: FlowContent.() -> Unit,
|
||||
) {
|
||||
internal fun spcPage(content: Data<PageFragment>) = HtmlPage {
|
||||
val title by pageMeta.string { SPC_TITLE }
|
||||
val pageName by pageMeta.string { title }
|
||||
spcHead(pageName)
|
||||
@ -30,7 +28,8 @@ context(WebPage) internal fun HTML.spcPageContent(
|
||||
}
|
||||
pageMeta["image"]?.let { imageMeta ->
|
||||
val imagePath =
|
||||
imageMeta.value?.string ?: imageMeta["path"].string ?: error("Image path not provided")
|
||||
imageMeta.value?.string ?: imageMeta["path"].string
|
||||
?: error("Image path not provided")
|
||||
val imageClass = imageMeta["position"].string ?: "main"
|
||||
span("image $imageClass") {
|
||||
img {
|
||||
@ -39,7 +38,7 @@ context(WebPage) internal fun HTML.spcPageContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment()
|
||||
fragment(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,34 +48,7 @@ context(WebPage) internal fun HTML.spcPageContent(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val FortyDataRenderer: DataRenderer = object : DataRenderer {
|
||||
|
||||
context(SiteBuilder)
|
||||
override fun invoke(name: Name, data: Data<Any>) {
|
||||
if (data.type == typeOf<HtmlFragment>()) {
|
||||
data as Data<HtmlFragment>
|
||||
val languageMeta: Meta = Language.forName(name)
|
||||
|
||||
val dataMeta: Meta = if (languageMeta.isEmpty()) {
|
||||
data.meta
|
||||
} else {
|
||||
data.meta.toMutableMeta().apply {
|
||||
Language.LANGUAGES_KEY put languageMeta
|
||||
}
|
||||
}
|
||||
|
||||
page(name, dataMeta) {
|
||||
spcPageContent {
|
||||
htmlData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
context(WebPage) private fun HTML.spcHomePage() {
|
||||
internal val spcHomePage = HtmlPage {
|
||||
spcHead()
|
||||
body("is-preload") {
|
||||
wrapper {
|
||||
@ -110,23 +82,23 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
// Main
|
||||
div {
|
||||
id = "main"
|
||||
section {
|
||||
div("inner home_creationinfo") {
|
||||
a(href = "https://mipt.ru/education/departments/fpmi/") {
|
||||
span("image") {
|
||||
img {
|
||||
src = "images/FPMI.jpg"
|
||||
alt = "FPMI"
|
||||
height = "60dp"
|
||||
width = "60dp"
|
||||
}
|
||||
}
|
||||
}
|
||||
p {
|
||||
+"Centre was created in 2022 based on the Phystech School of Applied Mathematics and Informatics at MIPT"
|
||||
}
|
||||
}
|
||||
}
|
||||
// section {
|
||||
// div("inner home_creationinfo") {
|
||||
// a(href = "https://mipt.ru/education/departments/fpmi/") {
|
||||
// span("image") {
|
||||
// img {
|
||||
// src = "images/FPMI.jpg"
|
||||
// alt = "FPMI"
|
||||
// height = "60dp"
|
||||
// width = "60dp"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// p {
|
||||
// +"Centre was created in 2022 based on the Phystech School of Applied Mathematics and Informatics at MIPT"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
section {
|
||||
div("inner") {
|
||||
@ -156,14 +128,14 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
article {
|
||||
span("image") {
|
||||
img {
|
||||
src = resolveRef("images/pic01.jpg")
|
||||
src = page.resolveRef("images/pic01.jpg")
|
||||
alt = ""
|
||||
}
|
||||
}
|
||||
header("major") {
|
||||
h3 {
|
||||
a(classes = "link") {
|
||||
href = resolvePageRef("education")
|
||||
href = page.resolvePageRef("education")
|
||||
+"""Education"""
|
||||
}
|
||||
}
|
||||
@ -173,14 +145,14 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
article {
|
||||
span("image") {
|
||||
img {
|
||||
src = resolveRef("images/pic02.jpg")
|
||||
src = page.resolveRef("images/pic02.jpg")
|
||||
alt = ""
|
||||
}
|
||||
}
|
||||
header("major") {
|
||||
h3 {
|
||||
a(classes = "link") {
|
||||
href = resolvePageRef("research")
|
||||
href = page.resolvePageRef("research")
|
||||
+"""Research"""
|
||||
}
|
||||
}
|
||||
@ -192,14 +164,14 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
article {
|
||||
span("image") {
|
||||
img {
|
||||
src = resolveRef("images/pic03.jpg")
|
||||
src = page.resolveRef("images/pic03.jpg")
|
||||
alt = ""
|
||||
}
|
||||
}
|
||||
header("major") {
|
||||
h3 {
|
||||
a(classes = "link") {
|
||||
href = resolvePageRef("consulting.index")
|
||||
href = page.resolvePageRef("consulting.index")
|
||||
+"""Consulting"""
|
||||
}
|
||||
}
|
||||
@ -209,14 +181,14 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
article {
|
||||
span("image") {
|
||||
img {
|
||||
src = resolveRef("images/pic04.jpg")
|
||||
src = page.resolveRef("images/pic04.jpg")
|
||||
alt = ""
|
||||
}
|
||||
}
|
||||
header("major") {
|
||||
h3 {
|
||||
a(classes = "link") {
|
||||
href = resolvePageRef("team")
|
||||
href = page.resolvePageRef("team")
|
||||
+"""Team"""
|
||||
}
|
||||
}
|
||||
@ -263,32 +235,37 @@ context(WebPage) private fun HTML.spcHomePage() {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SiteBuilder.spcHome(homePageData: DataTree<Any>, prefix: Name = Name.EMPTY) {
|
||||
private fun SiteContextWithData.allPagesIn(location: String) {
|
||||
siteData.filterByType<PageFragment> { name, meta, _ ->
|
||||
name.startsWith(location) && meta["type"].string == "page"
|
||||
}.forEach { (name, content) ->
|
||||
page(name, content = spcPage(content), pageMeta = content.meta)
|
||||
}
|
||||
}
|
||||
|
||||
//val homePageData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
|
||||
|
||||
site(prefix, homePageData) {
|
||||
internal val spcHome: HtmlSite = HtmlSite {
|
||||
multiLanguageSite(
|
||||
Language("en", route = Name.EMPTY),
|
||||
Language("ru"),
|
||||
) {
|
||||
static("assets")
|
||||
static("images")
|
||||
static("common", "")
|
||||
|
||||
withLanguages(
|
||||
"en" to "",
|
||||
"ru" to "ru"
|
||||
) {
|
||||
page { spcHomePage() }
|
||||
page(content = spcHomePage, pageMeta = siteData.meta ?: Meta.EMPTY)
|
||||
|
||||
localizedPages("consulting", dataRenderer = FortyDataRenderer)
|
||||
allPagesIn("consulting")
|
||||
|
||||
localizedPages("education", dataRenderer = FortyDataRenderer)
|
||||
allPagesIn("education")
|
||||
|
||||
spcSpotlight("team") { _, meta ->
|
||||
meta["type"].string == "team"
|
||||
}
|
||||
spcSpotlight("team") { _, meta ->
|
||||
meta["type"].string == "team"
|
||||
}
|
||||
|
||||
spcSpotlight("research") { name, meta ->
|
||||
name.startsWith("projects".asName()) && meta["type"].string == "project"
|
||||
}
|
||||
spcSpotlight("research") { name, meta ->
|
||||
name.startsWith("projects".asName()) && meta["type"].string == "project"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ package center.sciprog
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.Data
|
||||
import space.kscience.dataforge.data.await
|
||||
import space.kscience.dataforge.data.getByType
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
@ -29,35 +29,38 @@ import kotlin.collections.set
|
||||
// }
|
||||
//}
|
||||
|
||||
private val HtmlData.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string
|
||||
private val HtmlData.name: String get() = meta["name"].string ?: error("Name not found")
|
||||
private val Data<PageFragment>.imagePath: String? get() = meta["image"]?.string ?: meta["image.path"].string
|
||||
private val Data<PageFragment>.name: String get() = meta["name"].string ?: error("Name not found")
|
||||
|
||||
context(WebPage) class MagProgSection(
|
||||
context(PageContext)
|
||||
class MagProgSection(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val style: String,
|
||||
val content: FlowContent.() -> Unit,
|
||||
)
|
||||
|
||||
context(WebPage) private fun wrapSection(
|
||||
context(PageContext)
|
||||
private fun wrapSection(
|
||||
id: String,
|
||||
title: String,
|
||||
sectionContent: FlowContent.() -> Unit,
|
||||
): MagProgSection = MagProgSection(id, title, "wrapper style1 fullscreen fade-up") {
|
||||
): MagProgSection = MagProgSection(id, title, "wrapper style1 fullscreen") {
|
||||
div("inner") {
|
||||
h2 { +title }
|
||||
sectionContent()
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage) private fun wrapSection(
|
||||
block: HtmlData,
|
||||
context(PageContextWithData)
|
||||
private fun wrapSection(
|
||||
section: Data<PageFragment>,
|
||||
idOverride: String? = null,
|
||||
): MagProgSection = wrapSection(
|
||||
idOverride ?: block.id,
|
||||
block.meta["section_title"]?.string ?: error("Section without title"),
|
||||
idOverride ?: section.id,
|
||||
section.meta["section_title"]?.string ?: error("Section without title"),
|
||||
) {
|
||||
htmlData(block)
|
||||
fragment(section)
|
||||
}
|
||||
|
||||
private val CONTENT_NODE_NAME = Name.EMPTY//"content".asName()
|
||||
@ -68,26 +71,29 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program"
|
||||
private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses"
|
||||
private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners"
|
||||
|
||||
context(WebPage) private fun FlowContent.programSection() {
|
||||
private val programSection = PageFragment {
|
||||
val programBlock = data.resolveHtmlOrNull(PROGRAM_PATH)!!
|
||||
val recommendedBlock = data.resolveHtmlOrNull(RECOMMENDED_COURSES_PATH)!!
|
||||
div("inner") {
|
||||
h2 { +"Учебная программа" }
|
||||
htmlData(programBlock)
|
||||
fragment(programBlock)
|
||||
button(classes = "fit collapsible") {
|
||||
attributes["data-target"] = "recommended-courses-content"
|
||||
+"Рекомендованные курсы"
|
||||
}
|
||||
div(classes = "collapsible-content") {
|
||||
id = "recommended-courses-content"
|
||||
htmlData(recommendedBlock)
|
||||
fragment(recommendedBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage) private fun FlowContent.partners() {
|
||||
//val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
|
||||
val partnersData: Meta = runBlocking { data.getByType<Meta>(PARTNERS_PATH)?.await() } ?: Meta.EMPTY
|
||||
|
||||
private val partners = PageFragment {
|
||||
val partnersData: Meta = runBlocking {
|
||||
//TODO add meta reading step to data preparation
|
||||
data.getByType<Meta>(PARTNERS_PATH)?.await()
|
||||
} ?: Meta.EMPTY
|
||||
div("inner") {
|
||||
h2 { +"Партнеры" }
|
||||
div("features") {
|
||||
@ -115,8 +121,8 @@ context(WebPage) private fun FlowContent.partners() {
|
||||
// val photo: String? by meta.string()
|
||||
//}
|
||||
|
||||
context(WebPage) private fun FlowContent.team() {
|
||||
val team = data.findByContentType("magprog_team").values.sortedBy { it.order }
|
||||
private val team = PageFragment {
|
||||
val team = data.findHtmlByContentType("magprog_team").values.sortedBy { it.order }
|
||||
|
||||
div("inner") {
|
||||
h2 { +"Команда" }
|
||||
@ -131,7 +137,7 @@ context(WebPage) private fun FlowContent.team() {
|
||||
alt = imagePath
|
||||
) {
|
||||
h3 { +member.name }
|
||||
htmlData(member)
|
||||
fragment(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,8 +176,8 @@ context(WebPage) private fun FlowContent.team() {
|
||||
// }
|
||||
}
|
||||
|
||||
context(WebPage) private fun FlowContent.mentors() {
|
||||
val mentors = data.findByContentType("magprog_mentor").entries.sortedBy { it.value.id }
|
||||
val mentors = PageFragment {
|
||||
val mentors = data.findHtmlByContentType("magprog_mentor").entries.sortedBy { it.value.id }
|
||||
|
||||
div("inner") {
|
||||
h2 {
|
||||
@ -200,7 +206,7 @@ context(WebPage) private fun FlowContent.mentors() {
|
||||
}
|
||||
val info = data.resolveHtmlOrNull(name.replaceLast { NameToken(it.body + "[info]") })
|
||||
if (info != null) {
|
||||
htmlData(info)
|
||||
fragment(info)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +214,8 @@ context(WebPage) private fun FlowContent.mentors() {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage) internal fun HTML.magProgHead(title: String) {
|
||||
context(PageContext)
|
||||
internal fun HTML.magProgHead(title: String) {
|
||||
head {
|
||||
title {
|
||||
+title
|
||||
@ -258,7 +265,8 @@ context(WebPage) internal fun HTML.magProgHead(title: String) {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage) internal fun BODY.magProgFooter() {
|
||||
context(PageContext)
|
||||
internal fun BODY.magProgFooter() {
|
||||
footer("wrapper style1-alt") {
|
||||
id = "footer"
|
||||
div("inner") {
|
||||
@ -297,72 +305,113 @@ context(WebPage) internal fun BODY.magProgFooter() {
|
||||
}
|
||||
}
|
||||
|
||||
context(SnarkContext) private val HtmlData.mentorPageId get() = "mentor-${id}"
|
||||
context(SnarkContext) private val Data<PageFragment>.mentorPageId get() = "mentor-${id}"
|
||||
|
||||
internal fun SiteBuilder.spcMasters(magProgData: DataTree<Any>, prefix: Name = "education.masters".parseAsName()) {
|
||||
|
||||
//val magProgData: DataTree<Any> = snark.readDirectory(dataPath.resolve("content"))
|
||||
internal val spcMasters = HtmlSite {
|
||||
static("assets")
|
||||
static("images")
|
||||
static("common", "")
|
||||
|
||||
site(prefix, magProgData) {
|
||||
static("assets")
|
||||
static("images")
|
||||
static("common", "")
|
||||
page {
|
||||
val sections = listOf(
|
||||
wrapSection(data.resolveHtmlOrNull(INTRO_PATH)!!, "intro"),
|
||||
MagProgSection(
|
||||
id = "partners",
|
||||
title = "Партнеры",
|
||||
style = "wrapper style3 fullscreen fade-up"
|
||||
) {
|
||||
fragment(partners)
|
||||
},
|
||||
// section(props.data.partners),
|
||||
MagProgSection(
|
||||
id = "mentors",
|
||||
title = "Научные руководители",
|
||||
style = "wrapper style2 spotlights fade-up",
|
||||
) {
|
||||
fragment(mentors)
|
||||
},
|
||||
MagProgSection(
|
||||
id = "program",
|
||||
title = "Учебная программа",
|
||||
style = "wrapper style3 fullscreen fade-up"
|
||||
) {
|
||||
fragment(programSection)
|
||||
},
|
||||
wrapSection(data.resolveHtmlOrNull(ENROLL_PATH)!!, "enroll"),
|
||||
wrapSection(id = "contacts", title = "Контакты") {
|
||||
fragment(data.resolveHtmlOrNull(CONTACTS_PATH)!!)
|
||||
fragment(team)
|
||||
}
|
||||
)
|
||||
|
||||
page {
|
||||
val sections = listOf<MagProgSection>(
|
||||
wrapSection(page.data.resolveHtmlOrNull(INTRO_PATH)!!, "intro"),
|
||||
MagProgSection(
|
||||
id = "partners",
|
||||
title = "Партнеры",
|
||||
style = "wrapper style3 fullscreen fade-up"
|
||||
) {
|
||||
partners()
|
||||
},
|
||||
// section(props.data.partners),
|
||||
MagProgSection(
|
||||
id = "mentors",
|
||||
title = "Научные руководители",
|
||||
style = "wrapper style2 spotlights",
|
||||
) {
|
||||
mentors()
|
||||
},
|
||||
MagProgSection(
|
||||
id = "program",
|
||||
title = "Учебная программа",
|
||||
style = "wrapper style3 fullscreen fade-up"
|
||||
) {
|
||||
programSection()
|
||||
},
|
||||
wrapSection(page.data.resolveHtmlOrNull(ENROLL_PATH)!!, "enroll"),
|
||||
wrapSection(id = "contacts", title = "Контакты") {
|
||||
htmlData(page.data.resolveHtmlOrNull(CONTACTS_PATH)!!)
|
||||
team()
|
||||
}
|
||||
)
|
||||
|
||||
magProgHead("Магистратура \"Научное программирование\"")
|
||||
body("is-preload magprog-body") {
|
||||
section {
|
||||
id = "sidebar"
|
||||
div("inner") {
|
||||
nav {
|
||||
ul {
|
||||
magProgHead("Магистратура \"Научное программирование\"")
|
||||
body("is-preload magprog-body") {
|
||||
section {
|
||||
id = "sidebar"
|
||||
div("inner") {
|
||||
nav {
|
||||
ul {
|
||||
li {
|
||||
a(
|
||||
classes = "spc-home",
|
||||
href = resolvePageRef(
|
||||
Name.EMPTY,
|
||||
site.parent!!
|
||||
)
|
||||
) {
|
||||
i("fa fa-home") {
|
||||
attributes["aria-hidden"] = "true"
|
||||
}
|
||||
+"SPC"
|
||||
}
|
||||
}
|
||||
sections.forEach { section ->
|
||||
li {
|
||||
a(
|
||||
classes = "spc-home",
|
||||
href = "/" //TODO provide a way to navigate out-of-site
|
||||
) {
|
||||
i("fa fa-home") {
|
||||
attributes["aria-hidden"] = "true"
|
||||
}
|
||||
+"SPC"
|
||||
a(href = "#${section.id}") {
|
||||
+section.title
|
||||
}
|
||||
}
|
||||
sections.forEach { section ->
|
||||
li {
|
||||
a(href = "#${section.id}") {
|
||||
+section.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
id = "wrapper"
|
||||
sections.forEach { sec ->
|
||||
section(sec.style) {
|
||||
id = sec.id
|
||||
with(sec) { content() }
|
||||
}
|
||||
}
|
||||
}
|
||||
magProgFooter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val mentors = siteData.findHtmlByContentType("magprog_mentor").values.sortedBy {
|
||||
it.order
|
||||
}
|
||||
|
||||
mentors.forEach { mentor ->
|
||||
page(mentor.mentorPageId.asName(), siteData) {
|
||||
magProgHead("Научное программирование: ${mentor.name}")
|
||||
body("is-preload") {
|
||||
header {
|
||||
id = "header"
|
||||
a(classes = "title") {
|
||||
href = "${page.homeRef}#mentors"
|
||||
+"Научные руководители"
|
||||
}
|
||||
nav {
|
||||
ul {
|
||||
mentors.forEach {
|
||||
li {
|
||||
a {
|
||||
href = page.resolvePageRef(it.mentorPageId)
|
||||
+it.name.substringAfterLast(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -371,68 +420,25 @@ internal fun SiteBuilder.spcMasters(magProgData: DataTree<Any>, prefix: Name = "
|
||||
}
|
||||
div {
|
||||
id = "wrapper"
|
||||
sections.forEach { sec ->
|
||||
section(sec.style) {
|
||||
id = sec.id
|
||||
with(sec) { content() }
|
||||
section("wrapper") {
|
||||
id = "main"
|
||||
div("inner") {
|
||||
h1("major") { +mentor.name }
|
||||
val imageClass = mentor.meta["image.position"].string ?: "left"
|
||||
span("image $imageClass") {
|
||||
mentor.imagePath?.let { photoPath ->
|
||||
img(
|
||||
src = page.resolveRef(photoPath),
|
||||
alt = mentor.name
|
||||
)
|
||||
}
|
||||
}
|
||||
fragment(mentor)
|
||||
}
|
||||
}
|
||||
}
|
||||
magProgFooter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val mentors = data.findByContentType("magprog_mentor").values.sortedBy {
|
||||
it.order
|
||||
}
|
||||
|
||||
mentors.forEach { mentor ->
|
||||
page(mentor.mentorPageId.asName()) {
|
||||
|
||||
magProgHead("Научное программирование: ${mentor.name}")
|
||||
body("is-preload") {
|
||||
header {
|
||||
id = "header"
|
||||
a(classes = "title") {
|
||||
href = "$homeRef#mentors"
|
||||
+"Научные руководители"
|
||||
}
|
||||
nav {
|
||||
ul {
|
||||
mentors.forEach {
|
||||
li {
|
||||
a {
|
||||
href = resolvePageRef(it.mentorPageId)
|
||||
+it.name.substringAfterLast(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
id = "wrapper"
|
||||
section("wrapper") {
|
||||
id = "main"
|
||||
div("inner") {
|
||||
h1("major") { +mentor.name }
|
||||
val imageClass = mentor.meta["image.position"].string ?: "left"
|
||||
span("image $imageClass") {
|
||||
mentor.imagePath?.let { photoPath ->
|
||||
img(
|
||||
src = resolveRef(photoPath),
|
||||
alt = mentor.name
|
||||
)
|
||||
}
|
||||
}
|
||||
htmlData(mentor)
|
||||
}
|
||||
}
|
||||
}
|
||||
magProgFooter()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
package center.sciprog
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.snark.html.WebPage
|
||||
import space.kscience.snark.html.homeRef
|
||||
import space.kscience.snark.html.languages
|
||||
import space.kscience.snark.html.resolvePageRef
|
||||
import space.kscience.snark.html.*
|
||||
import java.time.LocalDate
|
||||
|
||||
|
||||
internal const val SPC_TITLE = "Scientific Programming Centre"
|
||||
|
||||
context(WebPage)
|
||||
context(PageContext)
|
||||
internal fun HTML.spcHead(title: String = SPC_TITLE) {
|
||||
head {
|
||||
title {
|
||||
@ -53,8 +49,7 @@ internal fun HTML.spcHead(title: String = SPC_TITLE) {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage)
|
||||
internal fun FlowContent.spcHomeMenu() {
|
||||
context(PageContext, FlowContent) internal fun spcHomeMenu() {
|
||||
nav {
|
||||
id = "menu"
|
||||
ul("links") {
|
||||
@ -106,8 +101,8 @@ internal fun FlowContent.spcHomeMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage)
|
||||
internal fun FlowContent.spcFooter() {
|
||||
context(PageContext, FlowContent)
|
||||
internal fun spcFooter() {
|
||||
footer {
|
||||
id = "footer"
|
||||
div("inner") {
|
||||
@ -158,8 +153,8 @@ internal fun FlowContent.spcFooter() {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage)
|
||||
internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
|
||||
context(PageContextWithData, FlowContent)
|
||||
internal fun wrapper(contentBody: FlowContent.() -> Unit) = postprocess {
|
||||
div {
|
||||
id = "wrapper"
|
||||
// Header
|
||||
@ -172,11 +167,16 @@ internal fun FlowContent.wrapper(contentBody: FlowContent.() -> Unit) {
|
||||
}
|
||||
|
||||
|
||||
if (languages.isNotEmpty()) {
|
||||
languageMap.takeIf { it.size > 1 }?.let { languageMap ->
|
||||
div {
|
||||
languages.forEach { (key, meta) ->
|
||||
a(classes = "button primary small") {
|
||||
href = resolvePageRef(meta["target"].string ?: "#")
|
||||
languageMap.entries.forEachIndexed { index, (key, meta) ->
|
||||
if (index != 0) {
|
||||
a {
|
||||
+"/"
|
||||
}
|
||||
}
|
||||
a {
|
||||
href = page.resolvePageRef(meta.string ?: "#", site.parent!!)
|
||||
+key
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
package center.sciprog
|
||||
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.branch
|
||||
import space.kscience.dataforge.data.putAll
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.snark.html.SiteBuilder
|
||||
import space.kscience.snark.html.HtmlSite
|
||||
import space.kscience.snark.html.site
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
private fun <T : Any> DataSet<T>.siteData(branchName: String): DataTree<T> = DataTree(dataType) {
|
||||
populateFrom(branch(Name.of(branchName, "content")))
|
||||
node("common", branch("common"))
|
||||
node("assets", branch(Name.of(branchName, "assets")))
|
||||
node("images", branch(Name.of(branchName, "images")))
|
||||
private inline fun <reified T> DataTree<T>.contentFor(branchName: String): DataTree<T> = DataTree{
|
||||
putAll(branch(Name.of(branchName, "content")) ?: DataTree.EMPTY)
|
||||
branch("common", branch("common") ?: DataTree.EMPTY)
|
||||
branch("assets", branch(Name.of(branchName, "assets")) ?: DataTree.EMPTY)
|
||||
branch("images", branch(Name.of(branchName, "images")) ?: DataTree.EMPTY)
|
||||
}
|
||||
|
||||
fun SiteBuilder.spcSite() {
|
||||
spcHome(data.siteData("home"))
|
||||
spcMasters(data.siteData("magprog"))
|
||||
val spcSite = HtmlSite {
|
||||
route(Name.EMPTY, siteData.contentFor("home"), content = spcHome)
|
||||
site("education.masters", siteData.contentFor("magprog"), content = spcMasters)
|
||||
// bmk(data.branch("bmk").withBranch("common", commonData))
|
||||
}
|
@ -1,18 +1,25 @@
|
||||
package center.sciprog
|
||||
|
||||
import space.kscience.dataforge.context.Global
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.snark.html.SiteBuilder
|
||||
import space.kscience.snark.html.SnarkHtmlPlugin
|
||||
import space.kscience.snark.html.readResources
|
||||
import space.kscience.snark.html.static
|
||||
import space.kscience.dataforge.workspace.resources
|
||||
import space.kscience.snark.html.SnarkHtml
|
||||
import space.kscience.snark.html.readSiteData
|
||||
import space.kscience.snark.html.static.staticSite
|
||||
import java.nio.file.Path
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
suspend fun main(args: Array<String>) = coroutineScope {
|
||||
val context = Context {
|
||||
plugin(SnarkHtml)
|
||||
}
|
||||
|
||||
val destinationPath = args.firstOrNull() ?: "build/public"
|
||||
|
||||
val snark = Global.request(SnarkHtmlPlugin)
|
||||
val siteData = snark.readResources("common", "home", "magprog")
|
||||
val snark = context.request(SnarkHtml)
|
||||
val siteData = snark.readSiteData(context) {
|
||||
resources(snark.io,"common", "home", "magprog")
|
||||
}
|
||||
|
||||
snark.static(siteData, Path.of(destinationPath), block = SiteBuilder::spcSite)
|
||||
snark.staticSite(siteData, Path.of(destinationPath), content = spcSite)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package html5up.forty
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.snark.html.WebPage
|
||||
import space.kscience.snark.html.PageContext
|
||||
|
||||
|
||||
internal fun FlowContent.fortyMenu() {
|
||||
@ -200,7 +200,8 @@ internal fun FlowContent.fortyFooter() {
|
||||
}
|
||||
}
|
||||
|
||||
context(WebPage) internal fun BODY.fortyScripts() {
|
||||
context(PageContext)
|
||||
internal fun BODY.fortyScripts() {
|
||||
script {
|
||||
src = resolveRef("assets/js/jquery.min.js")
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package html5up.forty
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.snark.html.WebPage
|
||||
import space.kscience.snark.html.PageContext
|
||||
|
||||
context(WebPage) internal fun HTML.landing(){
|
||||
context(PageContext)
|
||||
internal fun HTML.landing() {
|
||||
head {
|
||||
title {
|
||||
}
|
||||
@ -30,7 +31,7 @@ context(WebPage) internal fun HTML.landing(){
|
||||
div {
|
||||
id = "wrapper"
|
||||
// Header
|
||||
// Note: The "styleN" class below should match that of the banner element. -->
|
||||
// Note: The "styleN" class below should match that of the banner element. -->
|
||||
|
||||
header("alt style2") {
|
||||
id = "header"
|
||||
@ -48,7 +49,7 @@ context(WebPage) internal fun HTML.landing(){
|
||||
}
|
||||
fortyMenu()
|
||||
// Banner
|
||||
// Note: The "styleN" class below should match that of the header element.
|
||||
// Note: The "styleN" class below should match that of the header element.
|
||||
section("style2") {
|
||||
id = "banner"
|
||||
div("inner") {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package html5up.forty
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.snark.html.WebPage
|
||||
import space.kscience.snark.html.PageContext
|
||||
|
||||
context(WebPage) internal fun HTML.fortyPage(){
|
||||
context(PageContext)
|
||||
internal fun HTML.fortyPage() {
|
||||
head {
|
||||
title {
|
||||
}
|
||||
|