├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── cmd └── src │ └── main │ └── scala │ └── io │ └── udash │ └── generator │ ├── CmdDecisionMaker.scala │ └── Launcher.scala ├── core └── src │ └── main │ ├── resources │ └── images │ │ ├── icon_avsystem.png │ │ ├── icon_github.png │ │ ├── icon_stackoverflow.png │ │ ├── udash_logo.png │ │ └── udash_logo_m.png │ └── scala │ └── io │ └── udash │ └── generator │ ├── Generator.scala │ ├── GeneratorPlugin.scala │ ├── GeneratorSettings.scala │ ├── configuration │ ├── ConfigurationBuilder.scala │ └── decisions.scala │ ├── exceptions │ └── package.scala │ ├── plugins │ ├── Placeholder.scala │ ├── PlaceholdersCleanPlugin.scala │ ├── core │ │ ├── CoreDemosPlugin.scala │ │ └── CorePlugin.scala │ ├── jetty │ │ └── JettyLauncherPlugin.scala │ ├── rpc │ │ ├── RPCDemosPlugin.scala │ │ └── RPCPlugin.scala │ ├── sbt │ │ ├── SBTBootstrapPlugin.scala │ │ ├── SBTModulesPlugin.scala │ │ └── SBTProjectFiles.scala │ ├── scalacss │ │ └── ScalaCSSDemosPlugin.scala │ └── utils │ │ ├── FrontendPaths.scala │ │ └── UtilPaths.scala │ └── utils │ ├── FileOps.scala │ └── package.scala ├── dist ├── run.bat └── run.sh ├── project ├── build.properties └── plugins.sbt ├── test.sh └── test ├── basic-front-only.cnf ├── basic-front.cnf ├── demos-front-only.cnf ├── demos-front.cnf ├── full-com-example.cnf ├── full-custom-modules-retries.cnf ├── full-custom-modules.cnf ├── full.cnf ├── jetty.cnf └── rpc.cnf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Declare files that will always have LF line endings on checkout. 5 | *.sh text eol=lf 6 | 7 | # Declare files that will always have CRLF line endings on checkout. 8 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Eclipse template 3 | *.pydevproject 4 | .metadata 5 | .gradle 6 | bin/ 7 | tmp/ 8 | *.tmp 9 | *.bak 10 | *.swp 11 | *~.nib 12 | local.properties 13 | .settings/ 14 | .loadpath 15 | 16 | # Eclipse Core 17 | .project 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # JDT-specific (Eclipse Java Development Tools) 29 | .classpath 30 | 31 | # Java annotation processor (APT) 32 | .factorypath 33 | 34 | # PDT-specific 35 | .buildpath 36 | 37 | # sbteclipse plugin 38 | .target 39 | 40 | # TeXlipse plugin 41 | .texlipse 42 | ### Maven template 43 | target/ 44 | pom.xml.tag 45 | pom.xml.releaseBackup 46 | pom.xml.versionsBackup 47 | pom.xml.next 48 | release.properties 49 | dependency-reduced-pom.xml 50 | buildNumber.properties 51 | .mvn/timing.properties 52 | ### JetBrains template 53 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 54 | 55 | *.iml 56 | 57 | ## Directory-based project format: 58 | .idea/ 59 | # if you remove the above rule, at least ignore the following: 60 | 61 | # User-specific stuff: 62 | # .idea/workspace.xml 63 | # .idea/tasks.xml 64 | # .idea/dictionaries 65 | 66 | # Sensitive or high-churn files: 67 | # .idea/dataSources.ids 68 | # .idea/dataSources.xml 69 | # .idea/sqlDataSources.xml 70 | # .idea/dynamic.xml 71 | # .idea/uiDesigner.xml 72 | 73 | # Gradle: 74 | # .idea/gradle.xml 75 | # .idea/libraries 76 | 77 | # Mongo Explorer plugin: 78 | # .idea/mongoSettings.xml 79 | 80 | ## File-based project format: 81 | *.ipr 82 | *.iws 83 | 84 | ## Plugin-specific files: 85 | 86 | # IntelliJ 87 | /out/ 88 | 89 | # mpeltonen/sbt-idea plugin 90 | .idea_modules/ 91 | 92 | # JIRA plugin 93 | atlassian-ide-plugin.xml 94 | 95 | # Crashlytics plugin (for Android Studio and IntelliJ) 96 | com_crashlytics_export_strings.xml 97 | crashlytics.properties 98 | crashlytics-build.properties 99 | ### Java template 100 | *.class 101 | 102 | # Mobile Tools for Java (J2ME) 103 | .mtj.tmp/ 104 | 105 | # Package Files # 106 | *.jar 107 | *.war 108 | *.ear 109 | 110 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 111 | hs_err_pid* 112 | ### Scala template 113 | *.class 114 | *.log 115 | 116 | # sbt specific 117 | .cache 118 | .history 119 | .lib/ 120 | dist/* 121 | !dist/run.bat 122 | !dist/run.sh 123 | target/ 124 | lib_managed/ 125 | src_managed/ 126 | project/boot/ 127 | project/plugins/project/ 128 | 129 | # Scala-IDE specific 130 | .scala_dependencies 131 | .worksheet 132 | 133 | /udash-app 134 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.12.2 5 | 6 | jdk: 7 | - oraclejdk8 8 | 9 | script: 10 | - ./test.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Deprecated. Use [Udash Giter8 Template](https://github.com/UdashFramework/udash.g8) instead. 2 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "udash-generator" 2 | 3 | version in ThisBuild := "0.5.0-SNAPSHOT" 4 | organization in ThisBuild := "io.udash" 5 | scalaVersion in ThisBuild := "2.12.2" 6 | 7 | lazy val generator = project.in(file(".")) 8 | .aggregate(core, cmd) 9 | .settings(publishArtifact := false) 10 | 11 | lazy val core = project.in(file("core")) 12 | .settings(libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.3" % Test) 13 | 14 | lazy val cmd = project.in(file("cmd")) 15 | .dependsOn(core) 16 | .settings( 17 | assemblyJarName in assembly := "udash-generator.jar", 18 | mainClass in assembly := Some("io.udash.generator.Launcher") 19 | ) -------------------------------------------------------------------------------- /cmd/src/main/scala/io/udash/generator/CmdDecisionMaker.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.configuration._ 6 | import io.udash.generator.exceptions.InvalidConfigDecisionResponse 7 | 8 | import scala.io.StdIn 9 | 10 | class CmdDecisionMaker extends DecisionMaker { 11 | private val yesAnswers = Seq("yes", "y", "true", "t") 12 | 13 | override def makeDecision[T](d: Decision[T], current: GeneratorSettings): Decision[T] = { 14 | try { 15 | val response: Decision[T] = d match { 16 | case decision@RootDirectory(_) => 17 | RootDirectory(Some(askForFile("Project root directory", decision.default))) 18 | case decision@ClearRootDirectory(_) => 19 | ClearRootDirectory(Some(askForBoolean("Clear root directory", decision.default))) 20 | case decision@ProjectName(_) => 21 | ProjectName(Some(askForString("Project name", decision.default))) 22 | case decision@Organization(_) => 23 | Organization(Some(askForString("Organization", decision.default))) 24 | case decision@RootPackage(_) => 25 | RootPackage(Some(askForPackage("Root package", current.organization.split('.')))) 26 | case decision@ProjectTypeSelect(_) => 27 | ProjectTypeSelect(Some(askForSelection("Project type", decision.default, decision.options))) 28 | case decision@StdProjectTypeModulesSelect(_) => 29 | val backend = askForString("Backend module name", decision.default.backend) 30 | val shared = askForString("Shared module name", decision.default.shared) 31 | val frontend = askForString("Frontend module name", decision.default.frontend) 32 | StdProjectTypeModulesSelect(Some(StandardProject(backend, shared, frontend))) 33 | case decision@CreateBasicFrontendApp(_) => 34 | CreateBasicFrontendApp(Some(askForBoolean("Create basic frontend application", decision.default))) 35 | case decision@CreateFrontendDemos(_) => 36 | CreateFrontendDemos(Some(askForBoolean("Create frontend demo views", decision.default))) 37 | case decision@CreateScalaCSSDemos(_) => 38 | CreateScalaCSSDemos(Some(askForBoolean("Create ScalaCSS demo views", decision.default))) 39 | case decision@CreateJettyLauncher(_) => 40 | CreateJettyLauncher(Some(askForBoolean("Create Jetty launcher", decision.default))) 41 | case decision@CreateRPC(_) => 42 | CreateRPC(Some(askForBoolean("Create RPC communication layer", decision.default))) 43 | case decision@CreateRPCDemos(_) => 44 | CreateRPCDemos(Some(askForBoolean("Create RPC communication layer demos", decision.default))) 45 | case decision@EnableJsWorkbench(_) => 46 | EnableJsWorkbench(Some(askForBoolean("Enable JsWorkbench usage", decision.default))) 47 | case decision@RunGenerator(_) => 48 | RunGenerator(Some(askForBoolean("Start generation", decision.default))) 49 | } 50 | for (errors <- response.validator()) throw InvalidConfigDecisionResponse(errors) 51 | response 52 | } catch { 53 | case InvalidConfigDecisionResponse(ex) => 54 | println(ex) 55 | makeDecision(d, current) 56 | case _: Exception => 57 | makeDecision(d, current) 58 | } 59 | } 60 | 61 | private def ask[T](prompt: String, default: T)(converter: String => T): T = { 62 | val response = StdIn.readLine(prompt).trim 63 | if (response.isEmpty) default else converter(response) 64 | } 65 | 66 | private def askWithDefault[T](prompt: String, default: T)(converter: String => T): T = 67 | ask(s"$prompt [$default]: ", default)(converter) 68 | 69 | private def askForString(prompt: String, default: String): String = 70 | askWithDefault(prompt, default)(s => s) 71 | 72 | private def askForBoolean(prompt: String, default: Boolean): Boolean = 73 | askWithDefault(prompt, default)(r => yesAnswers.contains(r.toLowerCase)) 74 | 75 | private def askForPackage(prompt: String, default: Seq[String]): Seq[String] = 76 | ask(s"$prompt [${default.mkString(".")}]: ", default)(s => s.split("\\.")) 77 | 78 | private def askForFile(prompt: String, default: File): File = 79 | ask(s"$prompt [${default.getAbsolutePath}]: ", default)(r => new File(r)) 80 | 81 | private def askForSelection[T](prompt: String, default: T, options: Seq[T]): T = { 82 | val optionsPresentation = options.zipWithIndex.map{ 83 | case decision@(opt, idx) => s" ${idx+1}. $opt \n" 84 | }.mkString 85 | ask(s"$prompt [${options.indexOf(default) + 1}]:\n${optionsPresentation}Select: ", default)(r => options(Integer.parseInt(r) - 1)) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cmd/src/main/scala/io/udash/generator/Launcher.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import io.udash.generator.configuration.ConfigurationBuilder 4 | 5 | object Launcher { 6 | def main(args: Array[String]) { 7 | val generator = new Generator 8 | val configBuilder = new ConfigurationBuilder(new CmdDecisionMaker) 9 | val config = configBuilder.build() 10 | 11 | generator.start(config.plugins, config.settings) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/resources/images/icon_avsystem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-generator/427576ee49d961fa7c81eea8fcc3f017a518bfda/core/src/main/resources/images/icon_avsystem.png -------------------------------------------------------------------------------- /core/src/main/resources/images/icon_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-generator/427576ee49d961fa7c81eea8fcc3f017a518bfda/core/src/main/resources/images/icon_github.png -------------------------------------------------------------------------------- /core/src/main/resources/images/icon_stackoverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-generator/427576ee49d961fa7c81eea8fcc3f017a518bfda/core/src/main/resources/images/icon_stackoverflow.png -------------------------------------------------------------------------------- /core/src/main/resources/images/udash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-generator/427576ee49d961fa7c81eea8fcc3f017a518bfda/core/src/main/resources/images/udash_logo.png -------------------------------------------------------------------------------- /core/src/main/resources/images/udash_logo_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UdashFramework/udash-generator/427576ee49d961fa7c81eea8fcc3f017a518bfda/core/src/main/resources/images/udash_logo_m.png -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/Generator.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import io.udash.generator.utils.FileOps 4 | 5 | class Generator extends FileOps { 6 | /** 7 | * Starts project generation process. 8 | * 9 | * @param plugins Sequence of generator plugins, which will be fired. 10 | * @param settings Initial project settings. 11 | */ 12 | def start(plugins: Seq[GeneratorPlugin], settings: GeneratorSettings): GeneratorSettings = { 13 | if (settings.shouldRemoveExistingData) removeFileOrDir(settings.rootDirectory) 14 | settings.rootDirectory.mkdirs() 15 | plugins.foldLeft(settings)((settings: GeneratorSettings, plugin: GeneratorPlugin) => plugin.run(settings)) 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/GeneratorPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import io.udash.generator.utils.FileOps 4 | 5 | /** Part of generation chain. */ 6 | trait GeneratorPlugin extends FileOps { 7 | /** Starts plugins work with current project settings. 8 | * @return Project settings which will be passed to next plugin in generator sequence. */ 9 | def run(settings: GeneratorSettings): GeneratorSettings 10 | 11 | /** Plugin should start after `dependencies` plugins if they were selected. */ 12 | val dependencies: Seq[GeneratorPlugin] = Seq() 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/GeneratorSettings.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import java.io.File 4 | 5 | /** 6 | * Basic project configuration. 7 | * 8 | * @param rootDirectory Root directory of whole project. 9 | * @param shouldRemoveExistingData If `true`, generator will remove whole data from rootDirectory. 10 | * @param rootPackage Root package of sources. 11 | * @param projectType Project modules configuration. 12 | * @param organization Organization name. 13 | * @param projectName Project name. 14 | */ 15 | case class GeneratorSettings(rootDirectory: File, 16 | shouldRemoveExistingData: Boolean, 17 | projectName: String, 18 | organization: String, 19 | projectType: ProjectType, 20 | rootPackage: Seq[String], 21 | shouldEnableJsWorkbench: Boolean) { 22 | /** Root package of views in frontend. */ 23 | def viewsSubPackage: Seq[String] = Seq("views") 24 | /** Root package of styles in frontend. */ 25 | def stylesSubPackage: Seq[String] = Seq("styles") 26 | 27 | def scalaVersion: String = "2.12.2" 28 | def sbtVersion: String = "0.13.15" 29 | def scalaJSVersion: String = "0.6.18" 30 | def scalaCSSVersion: String = "0.5.3" 31 | def udashVersion: String = "0.5.0" 32 | def udashJQueryVersion: String = "1.0.1" 33 | def jettyVersion: String = "9.3.11.v20160721" 34 | def logbackVersion: String = "1.1.3" 35 | 36 | /** Application HTML root element id */ 37 | def htmlRootId: String = "application" 38 | 39 | /** Generated JS file with application code name (dev). */ 40 | def frontendImplFastJs: String = "frontend-impl-fast.js" 41 | /** Generated JS file with application code name (prod). */ 42 | def frontendImplJs: String = "frontend-impl.js" 43 | /** Generated JS file with dependencies code name (dev). */ 44 | def frontendDepsFastJs: String = "frontend-deps-fast.js" 45 | /** Generated JS file with dependencies code name (prod). */ 46 | def frontendDepsJs: String = "frontend-deps.js" 47 | 48 | /** Udash DevGuide root URL. */ 49 | def udashDevGuide: String = "http://guide.udash.io/" 50 | 51 | /** Assets images */ 52 | def imageResourcePath = "/images/" 53 | def assetsImages = Seq("icon_avsystem.png", "icon_github.png", "icon_stackoverflow.png", "udash_logo.png", "udash_logo_m.png") 54 | } 55 | 56 | sealed trait ProjectType 57 | 58 | /** Project does not contain submodules, it's only frontend application and everything is compiled to JavaScript. */ 59 | case object FrontendOnlyProject extends ProjectType 60 | 61 | /** 62 | * Standard Udash project with three submodules: 63 | * 64 | * @param backend - module compiled to JVM name 65 | * @param shared - module compiled to JS and JVM name 66 | * @param frontend - module compiled to JS name 67 | */ 68 | case class StandardProject(backend: String, shared: String, frontend: String) extends ProjectType 69 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/configuration/ConfigurationBuilder.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.configuration 2 | 3 | import io.udash.generator._ 4 | import io.udash.generator.exceptions.InvalidConfigDecisionResponse 5 | import io.udash.generator.plugins.PlaceholdersCleanPlugin 6 | import io.udash.generator.plugins.core.{CoreDemosPlugin, CorePlugin} 7 | import io.udash.generator.plugins.jetty.JettyLauncherPlugin 8 | import io.udash.generator.plugins.rpc.{RPCDemosPlugin, RPCPlugin} 9 | import io.udash.generator.plugins.sbt.{SBTBootstrapPlugin, SBTModulesPlugin} 10 | import io.udash.generator.plugins.scalacss.ScalaCSSDemosPlugin 11 | 12 | import scala.annotation.tailrec 13 | import scala.collection.mutable 14 | 15 | trait DecisionMaker { 16 | /** Should return decision with filled response option. */ 17 | def makeDecision[T](decision: Decision[T], current: GeneratorSettings): Decision[T] 18 | } 19 | 20 | case class Configuration(plugins: Seq[GeneratorPlugin], settings: GeneratorSettings) 21 | 22 | /** Creates project configuration based on `decisionMaker` responses. */ 23 | class ConfigurationBuilder(decisionMaker: DecisionMaker) { 24 | def build(): Configuration = { 25 | @tailrec 26 | def _build(plugins: Seq[GeneratorPlugin], settings: GeneratorSettings)(decisions: List[Decision[_]]): Configuration = { 27 | if (decisions.isEmpty) Configuration(plugins, settings) 28 | else { 29 | val response = decisionMaker.makeDecision(decisions.head, settings) 30 | val errors: Option[String] = response.validator() 31 | if (errors.isDefined) throw InvalidConfigDecisionResponse(errors.get) 32 | _build( 33 | plugins ++ selectPlugins(response), 34 | changeSettings(response, settings) 35 | )(selectNextDecisions(response, settings) ++ decisions.tail) 36 | } 37 | } 38 | 39 | def dependenciesOrder(plugins: Seq[GeneratorPlugin]): Seq[GeneratorPlugin] = { 40 | val visited = mutable.Set.empty[GeneratorPlugin] 41 | val builder = mutable.ArrayBuffer[GeneratorPlugin]() 42 | 43 | def visit(plugin: GeneratorPlugin): Unit = 44 | if (!visited.contains(plugin)) { 45 | visited += plugin 46 | plugin.dependencies.filter(plugins.contains).foreach(visit) 47 | builder.prepend(plugin) 48 | } 49 | 50 | plugins.foreach(visit) 51 | builder.reverseIterator.toSeq 52 | } 53 | 54 | val configuration = _build(Seq(), GeneratorSettings(null, false, null, null, null, null, false))(startingDecisions) 55 | val sortedPlugins = dependenciesOrder(configuration.plugins) 56 | configuration.copy(plugins = sortedPlugins) 57 | } 58 | 59 | private def selectPlugins(response: Decision[_]): Seq[GeneratorPlugin] = { 60 | response match { 61 | case ProjectTypeSelect(Some(FrontendOnlyProject)) => 62 | Seq(SBTBootstrapPlugin, SBTModulesPlugin) 63 | case StdProjectTypeModulesSelect(Some(projectType)) => 64 | Seq(SBTBootstrapPlugin, SBTModulesPlugin) 65 | case CreateBasicFrontendApp(Some(true)) => 66 | Seq(CorePlugin) 67 | case CreateFrontendDemos(Some(true)) => 68 | Seq(CoreDemosPlugin) 69 | case CreateScalaCSSDemos(Some(true)) => 70 | Seq(ScalaCSSDemosPlugin) 71 | case CreateJettyLauncher(Some(true)) => 72 | Seq(JettyLauncherPlugin) 73 | case CreateRPC(Some(true)) => 74 | Seq(RPCPlugin) 75 | case CreateRPCDemos(Some(true)) => 76 | Seq(RPCDemosPlugin) 77 | case RunGenerator(Some(true)) => 78 | Seq(PlaceholdersCleanPlugin) 79 | case _ => 80 | Seq.empty 81 | } 82 | } 83 | 84 | private def changeSettings(response: Decision[_], settings: GeneratorSettings): GeneratorSettings = { 85 | response match { 86 | case RootDirectory(Some(dir)) => 87 | settings.copy(rootDirectory = dir) 88 | case ClearRootDirectory(Some(clear)) => 89 | settings.copy(shouldRemoveExistingData = clear) 90 | case ProjectName(Some(name)) => 91 | settings.copy(projectName = name) 92 | case Organization(Some(name)) => 93 | settings.copy(organization = name) 94 | case RootPackage(Some(pck)) => 95 | settings.copy(rootPackage = pck) 96 | case ProjectTypeSelect(Some(FrontendOnlyProject)) => 97 | settings.copy(projectType = FrontendOnlyProject) 98 | case StdProjectTypeModulesSelect(Some(projectType)) => 99 | settings.copy(projectType = projectType) 100 | case EnableJsWorkbench(Some(enable)) => 101 | settings.copy(shouldEnableJsWorkbench = enable) 102 | case _ => 103 | settings 104 | } 105 | } 106 | 107 | private def selectNextDecisions(response: Decision[_], settings: GeneratorSettings): List[Decision[_]] = { 108 | response match { 109 | case ProjectTypeSelect(Some(FrontendOnlyProject)) => 110 | List(CreateBasicFrontendApp()) 111 | case ProjectTypeSelect(Some(_: StandardProject)) => 112 | List(StdProjectTypeModulesSelect()) 113 | case StdProjectTypeModulesSelect(Some(projectType)) => 114 | List(CreateBasicFrontendApp()) 115 | case CreateBasicFrontendApp(Some(true)) => 116 | settings.projectType match { 117 | case FrontendOnlyProject => 118 | List(CreateFrontendDemos(), CreateScalaCSSDemos()) 119 | case StandardProject(_, _, _) => 120 | List(CreateFrontendDemos(), CreateScalaCSSDemos(), CreateJettyLauncher()) 121 | } 122 | case CreateJettyLauncher(Some(true)) => 123 | List(CreateRPC()) 124 | case CreateRPC(Some(true)) => 125 | List(CreateRPCDemos()) 126 | case _ => 127 | List.empty 128 | } 129 | } 130 | 131 | private val startingDecisions: List[Decision[_]] = 132 | List[Decision[_]]( 133 | RootDirectory(), 134 | ClearRootDirectory(), 135 | ProjectName(), 136 | Organization(), 137 | RootPackage(), 138 | ProjectTypeSelect(), 139 | EnableJsWorkbench(), 140 | RunGenerator() 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/configuration/decisions.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.configuration 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.{FrontendOnlyProject, ProjectType, StandardProject} 6 | 7 | sealed abstract class Decision[ResponseType](val default: ResponseType) { 8 | def response: Option[ResponseType] 9 | def validator(): Option[String] = None 10 | } 11 | 12 | sealed abstract class SelectDecision[ResponseType](default: ResponseType) extends Decision[ResponseType](default) { 13 | def options: Seq[ResponseType] 14 | override def validator(): Option[String] = 15 | if (!options.contains(response.get)) Some(s"Select one of following values: $options") else None 16 | } 17 | 18 | sealed abstract class NonEmptyStringDecision(errorMsg: String, default: String) extends Decision[String](default) { 19 | override def validator(): Option[String] = 20 | if (response.get.isEmpty || response.get.trim.isEmpty) Some(errorMsg) else None 21 | } 22 | 23 | /** Project will be generated in this directory. */ 24 | case class RootDirectory(override val response: Option[File] = None) extends Decision[File](new File("udash-app")) { 25 | override def validator(): Option[String] = { 26 | val target = response.get 27 | if (target.exists() && !target.isDirectory) Some(s"${target.getAbsolutePath} is not a directory.") 28 | else if (target.exists() && !target.canWrite) Some(s"You can not write in ${target.getAbsolutePath}.") 29 | else None 30 | } 31 | } 32 | 33 | /** If `true`, root project directory will be cleared before generation. */ 34 | case class ClearRootDirectory(override val response: Option[Boolean] = None) extends Decision[Boolean](false) 35 | 36 | /** Generated project name. */ 37 | case class ProjectName(override val response: Option[String] = None) extends NonEmptyStringDecision("Project name cannot be empty!", "udash-app") { 38 | override def validator(): Option[String] = 39 | super.validator() match { 40 | case None => 41 | if ("[^A-Za-z\\d-]".r.findFirstIn(response.get).nonEmpty) Some("Project name can contain only characters, digits and '-' sign.") 42 | else None 43 | case error => error 44 | } 45 | } 46 | 47 | /** Organization name. */ 48 | case class Organization(override val response: Option[String] = None) extends NonEmptyStringDecision("Organization name cannot be empty!", "com.example") { 49 | override def validator(): Option[String] = 50 | super.validator() match { 51 | case None => 52 | if ("^[A-Za-z]+(\\.[A-Za-z][A-Za-z\\d-]*)*$".r.findFirstIn(response.get).isEmpty) 53 | Some( 54 | """Organization name should be provided in reverse domain notation, eg. 'io.udash'. 55 | |The first part can contain only characters, the others can contains characters, digits and '-' sign. 56 | """.stripMargin) 57 | else None 58 | case error => error 59 | } 60 | } 61 | 62 | /** Root source code package. */ 63 | case class RootPackage(override val response: Option[Seq[String]] = None) extends Decision[Seq[String]](Seq("com", "example")) { 64 | override def validator(): Option[String] = 65 | if (response.isEmpty || response.get.isEmpty) 66 | Some("Root package can not be empty.") 67 | else if (response.get.exists(part => !part.matches("^[A-Za-z][A-Za-z\\d]*$"))) 68 | Some( 69 | """Root package should be provided in reverse domain notation, eg. 'io.udash.app'. 70 | |The first part can contain only characters, the others can contains characters and digits. 71 | """.stripMargin) 72 | else None 73 | } 74 | 75 | /** Frontend-only or standard Udash project configuration. */ 76 | case class ProjectTypeSelect(override val response: Option[ProjectType] = None) extends SelectDecision[ProjectType](StandardProject("backend", "shared", "frontend")) { 77 | override val options: Seq[ProjectType] = Seq(FrontendOnlyProject, StandardProject("backend", "shared", "frontend")) 78 | } 79 | 80 | /** Names of modules in standard Udash project configuration. */ 81 | case class StdProjectTypeModulesSelect(override val response: Option[StandardProject] = None) extends Decision[StandardProject](StandardProject("backend", "shared", "frontend")) { 82 | override def validator(): Option[String] = { 83 | val project = response.get 84 | if (project.backend == project.frontend || project.backend == project.shared || project.frontend == project.shared) 85 | Some("Module names must be unique.") 86 | else if (project.backend.trim.isEmpty || project.shared.trim.isEmpty || project.frontend.trim.isEmpty) 87 | Some("Module names must not be empty.") 88 | else if (Seq(project.backend, project.frontend, project.shared).exists(name => "[^\\w\\d-]".r.findFirstIn(name).nonEmpty)) 89 | Some("Module name can contain only characters, digits and '-' sign.") 90 | else None 91 | } 92 | } 93 | 94 | /* If `true`, generator creates basic frontend application code. */ 95 | case class CreateBasicFrontendApp(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 96 | 97 | /* If `true`, generator creates frontend demo views. */ 98 | case class CreateFrontendDemos(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 99 | 100 | /* If `true`, generator creates ScalaCSS demo views. */ 101 | case class CreateScalaCSSDemos(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 102 | 103 | /* If `true`, generator creates Jetty server serving frontend application. */ 104 | case class CreateJettyLauncher(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 105 | 106 | /* If `true`, generator creates example RPC interfaces and implementation in `frontend` and `backend` modules. */ 107 | case class CreateRPC(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 108 | 109 | /* If `true`, generator creates RPC demo views. */ 110 | case class CreateRPCDemos(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 111 | 112 | /* If `true`, generator starts project generation process. */ 113 | case class RunGenerator(override val response: Option[Boolean] = None) extends Decision[Boolean](true) 114 | 115 | /* If `true`, generator enables the app to use the JsWorkbench SBT Plugin during development. */ 116 | case class EnableJsWorkbench(override val response: Option[Boolean] = None) extends Decision[Boolean](true) -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/exceptions/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | package object exceptions { 4 | case class FileCreationError(msg: String) extends RuntimeException(msg) 5 | case class FileDoesNotExist(msg: String) extends RuntimeException(msg) 6 | 7 | case class InvalidConfiguration(msg: String) extends RuntimeException(msg) 8 | 9 | case class InvalidConfigDecisionResponse(msg: String) extends RuntimeException(msg) 10 | } -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/Placeholder.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins 2 | 3 | sealed class Placeholder(str: String) { 4 | override def toString: String = s"/*<<$str>>*/" 5 | } 6 | 7 | case object UdashBuildPlaceholder extends Placeholder("udash-generator-custom-build") 8 | 9 | case object DependenciesPlaceholder extends Placeholder("udash-generator-dependencies") 10 | case object DependenciesVariablesPlaceholder extends Placeholder("udash-generator-dependencies-variables") 11 | case object DependenciesFrontendPlaceholder extends Placeholder("udash-generator-dependencies-frontend") 12 | case object DependenciesFrontendJSPlaceholder extends Placeholder("udash-generator-dependencies-frontendJS") 13 | case object DependenciesCrossPlaceholder extends Placeholder("udash-generator-dependencies-cross") 14 | case object DependenciesBackendPlaceholder extends Placeholder("udash-generator-dependencies-backend") 15 | 16 | case object RootSettingsPlaceholder extends Placeholder("udash-generator-root-settings") 17 | case object RootModulePlaceholder extends Placeholder("udash-generator-root-module") 18 | case object FrontendSettingsPlaceholder extends Placeholder("udash-generator-frontend-settings") 19 | case object FrontendModulePlaceholder extends Placeholder("udash-generator-frontend-module") 20 | case object BackendSettingsPlaceholder extends Placeholder("udash-generator-backend-settings") 21 | case object BackendModulePlaceholder extends Placeholder("udash-generator-backend-module") 22 | case object SharedSettingsPlaceholder extends Placeholder("udash-generator-shared-settings") 23 | case object SharedModulePlaceholder extends Placeholder("udash-generator-shared-module") 24 | case object SharedJSModulePlaceholder extends Placeholder("udash-generator-sharedJS-module") 25 | case object SharedJVMModulePlaceholder extends Placeholder("udash-generator-sharedJVM-module") 26 | 27 | case object HTMLHeadPlaceholder extends Placeholder("udash-generator-html-head") 28 | 29 | case object FrontendRoutingRegistryPlaceholder extends Placeholder("udash-generator-frontend-routing-registry") 30 | case object FrontendVPRegistryPlaceholder extends Placeholder("udash-generator-frontend-vp-registry") 31 | case object FrontendStatesRegistryPlaceholder extends Placeholder("udash-generator-frontend-states-registry") 32 | case object FrontendIndexMenuPlaceholder extends Placeholder("udash-generator-frontend-index-menu") 33 | case object FrontendContextPlaceholder extends Placeholder("udash-generator-frontend-context") 34 | case object FrontendAppInitPlaceholder extends Placeholder("udash-generator-frontend-app-init") 35 | case object FrontendImportsPlaceholder extends Placeholder("udash-generator-frontend-imports") 36 | 37 | case object FrontendStyledHeaderPlaceholder extends Placeholder("udash-generator-frontend-styled-header") 38 | case object FrontendStyledFooterPlaceholder extends Placeholder("udash-generator-frontend-styled-footer") 39 | 40 | case object FrontendStylesMainPlaceholder extends Placeholder("udash-generator-frontend-styles-main") 41 | case object FrontendStylesBodyPlaceHolder extends Placeholder("udash-generator-frontend-styles-body") 42 | case object FrontendStylesLinkBlackPlaceholder extends Placeholder("udash-generator-frontend-styles-link-black") 43 | case object FrontendStylesStepsListPlaceholder extends Placeholder("udash-generator-frontend-styles-steps-list") 44 | 45 | case object BackendAppServerPlaceholder extends Placeholder("udash-generator-backend-app-server") 46 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/PlaceholdersCleanPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.{GeneratorPlugin, GeneratorSettings} 6 | 7 | /** 8 | * Removes all placeholders from generated project. 9 | * Placeholders look like: /*<>*/ 10 | */ 11 | object PlaceholdersCleanPlugin extends GeneratorPlugin { 12 | override def run(settings: GeneratorSettings): GeneratorSettings = { 13 | cleanDirectory(settings.rootDirectory) 14 | settings 15 | } 16 | 17 | private def cleanDirectory(dir: File): Unit = 18 | dir.listFiles() 19 | .foreach(f => { 20 | val filename = f.getName.toLowerCase() 21 | if (f.isDirectory) cleanDirectory(f) 22 | else if (filename.endsWith("scala") || filename.endsWith("sbt") || filename.endsWith("html")) cleanFile(f) 23 | }) 24 | 25 | private def cleanFile(file: File): Unit = { 26 | removeFromFile(file)("/\\*<>\\*/") 27 | // Clear leading comma in dependencies 28 | replaceInFile(file)("\\(\\s*,", "(") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/core/CoreDemosPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.core 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.plugins._ 6 | import io.udash.generator.plugins.sbt.SBTProjectFiles 7 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 8 | import io.udash.generator.utils._ 9 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 10 | 11 | object CoreDemosPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 12 | 13 | override val dependencies = Seq(CorePlugin) 14 | 15 | override def run(settings: GeneratorSettings): GeneratorSettings = { 16 | val rootPck: File = settings.projectType match { 17 | case FrontendOnlyProject => 18 | rootPackageInSrc(settings.rootDirectory, settings) 19 | case StandardProject(_, shared, frontend) => 20 | rootPackageInSrc(settings.rootDirectory.subFile(frontend), settings) 21 | } 22 | val stateName = createDemoView(rootPck, settings) 23 | addIndexLink(rootPck, stateName) 24 | 25 | settings 26 | } 27 | 28 | private def addIndexLink(rootPackage: File, state: String): Unit = { 29 | val indexViewScala = viewsPackageInSrc(rootPackage).subFile("IndexView.scala") 30 | requireFilesExist(Seq(indexViewScala)) 31 | 32 | appendOnPlaceholder(indexViewScala)(FrontendIndexMenuPlaceholder, 33 | s""", 34 | | li(a(${FrontendStylesLinkBlackPlaceholder}href := $state().url)("Binding demo")), 35 | | li(a(${FrontendStylesLinkBlackPlaceholder}href := $state("From index").url)("Binding demo with URL argument"))""".stripMargin) 36 | } 37 | 38 | private def createDemoView(rootPackage: File, settings: GeneratorSettings): String = { 39 | val statesScala = rootPackage.subFile("states.scala") 40 | val routingRegistryDefScala = rootPackage.subFile("RoutingRegistryDef.scala") 41 | val statesToViewPresenterDefScala = rootPackage.subFile("StatesToViewPresenterDef.scala") 42 | 43 | val bindingDemoViewScala = viewsPackageInSrc(rootPackage).subFile("BindingDemoView.scala") 44 | val stateName = "BindingDemoState" 45 | 46 | requireFilesExist(Seq(viewsPackageInSrc(rootPackage), statesScala, routingRegistryDefScala, statesToViewPresenterDefScala)) 47 | createFiles(Seq(bindingDemoViewScala), requireNotExists = true) 48 | 49 | appendOnPlaceholder(statesScala)(FrontendStatesRegistryPlaceholder, 50 | s""" 51 | | 52 | |case class $stateName(urlArg: String = "") extends RoutingState(RootState)""".stripMargin) 53 | 54 | appendOnPlaceholder(routingRegistryDefScala)(FrontendRoutingRegistryPlaceholder, 55 | s""" 56 | | case "/binding" => $stateName("") 57 | | case "/binding" /:/ arg => $stateName(arg)""".stripMargin) 58 | 59 | appendOnPlaceholder(statesToViewPresenterDefScala)(FrontendVPRegistryPlaceholder, 60 | s""" 61 | | case $stateName(urlArg) => BindingDemoViewPresenter(urlArg)""".stripMargin) 62 | 63 | writeFile(bindingDemoViewScala)( 64 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 65 | | 66 | |import io.udash._ 67 | |import ${settings.rootPackage.mkPackage()}.$stateName 68 | |import org.scalajs.dom.Element$FrontendImportsPlaceholder 69 | | 70 | |case class BindingDemoViewPresenter(urlArg: String) extends DefaultViewPresenterFactory[$stateName](() => { 71 | | import ${settings.rootPackage.mkPackage()}.Context._ 72 | | 73 | | val model = Property[String](urlArg) 74 | | new BindingDemoView(model) 75 | |}) 76 | | 77 | |class BindingDemoView(model: Property[String]) extends View { 78 | | import scalatags.JsDom.all._ 79 | | 80 | | private val content = div( 81 | | h2( 82 | | "You can find this demo source code in: ", 83 | | i("${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.BindingDemoView") 84 | | ), 85 | | h3("Example"), 86 | | TextInput.debounced(model, placeholder := "Type something..."), 87 | | p("You typed: ", bind(model)), 88 | | h3("Read more"), 89 | | a$FrontendStylesLinkBlackPlaceholder(href := "${settings.udashDevGuide}#/frontend/bindings", target := "_blank")("Read more in Udash Guide.") 90 | | ) 91 | | 92 | | override def getTemplate: Modifier = content 93 | | 94 | | override def renderChild(view: View): Unit = {} 95 | |} 96 | |""".stripMargin 97 | ) 98 | 99 | stateName 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/core/CorePlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.core 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.plugins._ 6 | import io.udash.generator.plugins.sbt.{SBTBootstrapPlugin, SBTModulesPlugin, SBTProjectFiles} 7 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 8 | import io.udash.generator.utils._ 9 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 10 | 11 | object CorePlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 12 | 13 | override val dependencies = Seq(SBTBootstrapPlugin, SBTModulesPlugin) 14 | 15 | override def run(settings: GeneratorSettings): GeneratorSettings = { 16 | settings.projectType match { 17 | case FrontendOnlyProject => 18 | addUdashCoreDependency(settings) 19 | createJSApp(rootPackageInSrc(settings.rootDirectory, settings), settings) 20 | case StandardProject(_, shared, frontend) => 21 | addUdashCoreDependency(settings) 22 | createJSApp(rootPackageInSrc(settings.rootDirectory.subFile(frontend), settings), settings) 23 | } 24 | 25 | settings 26 | } 27 | 28 | private def addUdashCoreDependency(settings: GeneratorSettings): Unit = { 29 | requireFilesExist(Seq(dependenciesScala(settings))) 30 | 31 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesVariablesPlaceholder, 32 | s"""val udashVersion = "${settings.udashVersion}" 33 | | val udashJQueryVersion = "${settings.udashJQueryVersion}" 34 | | val logbackVersion = "${settings.logbackVersion}"""".stripMargin) 35 | 36 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesCrossPlaceholder, 37 | s""", 38 | | "io.udash" %%% "udash-core-shared" % udashVersion""".stripMargin) 39 | 40 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesFrontendPlaceholder, 41 | s""", 42 | | "io.udash" %%% "udash-core-frontend" % udashVersion, 43 | | "io.udash" %%% "udash-jquery" % udashJQueryVersion""".stripMargin) 44 | 45 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesBackendPlaceholder, 46 | s""", 47 | | "ch.qos.logback" % "logback-classic" % logbackVersion""".stripMargin) 48 | } 49 | 50 | private def createJSApp(rootPackage: File, settings: GeneratorSettings): Unit = { 51 | val initScala = rootPackage.subFile("init.scala") 52 | val statesScala = rootPackage.subFile("states.scala") 53 | val routingRegistryDefScala = rootPackage.subFile("RoutingRegistryDef.scala") 54 | val statesToViewPresenterDefScala = rootPackage.subFile("StatesToViewPresenterDef.scala") 55 | val rootViewScala = viewsPackageInSrc(rootPackage).subFile("RootView.scala") 56 | val indexViewScala = viewsPackageInSrc(rootPackage).subFile("IndexView.scala") 57 | val errorViewScala = viewsPackageInSrc(rootPackage).subFile("ErrorView.scala") 58 | 59 | createDirs(Seq(viewsPackageInSrc(rootPackage))) 60 | createFiles(Seq(initScala, statesScala, routingRegistryDefScala, statesToViewPresenterDefScala, 61 | rootViewScala, indexViewScala, errorViewScala), requireNotExists = true) 62 | 63 | writeFile(initScala)( 64 | s"""package ${settings.rootPackage.mkPackage()} 65 | | 66 | |import io.udash._ 67 | |import io.udash.wrappers.jquery._ 68 | |import org.scalajs.dom.{Element, document} 69 | | 70 | |import scala.scalajs.js.JSApp 71 | |import scala.scalajs.js.annotation.JSExport 72 | | 73 | |object Context { 74 | | implicit val executionContext = scalajs.concurrent.JSExecutionContext.Implicits.queue 75 | | private val routingRegistry = new RoutingRegistryDef 76 | | private val viewPresenterRegistry = new StatesToViewPresenterDef 77 | | 78 | | implicit val applicationInstance = new Application[RoutingState](routingRegistry, viewPresenterRegistry, RootState)$FrontendContextPlaceholder 79 | |} 80 | | 81 | |object Init extends StrictLogging { 82 | | import Context._ 83 | | 84 | | def main(args: Array[String]): Unit = { 85 | | jQ(document).ready((_: Element) => { 86 | | val appRoot = jQ("#${settings.htmlRootId}").get(0) 87 | | if (appRoot.isEmpty) { 88 | | logger.error("Application root element not found! Check your index.html file!") 89 | | } else { 90 | | applicationInstance.run(appRoot.get)$FrontendAppInitPlaceholder 91 | | } 92 | | }) 93 | | } 94 | |} 95 | |""".stripMargin 96 | ) 97 | 98 | writeFile(statesScala)( 99 | s"""package ${settings.rootPackage.mkPackage()} 100 | | 101 | |import io.udash._ 102 | | 103 | |sealed abstract class RoutingState(val parentState: RoutingState) extends State { 104 | | def url(implicit application: Application[RoutingState]): String = s"#${"${application.matchState(this).value}"}" 105 | |} 106 | | 107 | |case object RootState extends RoutingState(null) 108 | | 109 | |case object ErrorState extends RoutingState(RootState) 110 | | 111 | |case object IndexState extends RoutingState(RootState)$FrontendStatesRegistryPlaceholder 112 | |""".stripMargin 113 | ) 114 | 115 | writeFile(routingRegistryDefScala)( 116 | s"""package ${settings.rootPackage.mkPackage()} 117 | | 118 | |import io.udash._ 119 | |import io.udash.utils.Bidirectional 120 | | 121 | |class RoutingRegistryDef extends RoutingRegistry[RoutingState] { 122 | | def matchUrl(url: Url): RoutingState = 123 | | url2State.applyOrElse(url.value.stripSuffix("/"), (x: String) => ErrorState) 124 | | 125 | | def matchState(state: RoutingState): Url = 126 | | Url(state2Url.apply(state)) 127 | | 128 | | private val (url2State, state2Url) = Bidirectional[String, RoutingState] { 129 | | case "" => IndexState$FrontendRoutingRegistryPlaceholder 130 | | } 131 | |} 132 | |""".stripMargin 133 | ) 134 | 135 | writeFile(statesToViewPresenterDefScala)( 136 | s"""package ${settings.rootPackage.mkPackage()} 137 | | 138 | |import io.udash._ 139 | |import ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}._ 140 | | 141 | |class StatesToViewPresenterDef extends ViewPresenterRegistry[RoutingState] { 142 | | def matchStateToResolver(state: RoutingState): ViewPresenter[_ <: RoutingState] = state match { 143 | | case RootState => RootViewPresenter 144 | | case IndexState => IndexViewPresenter$FrontendVPRegistryPlaceholder 145 | | case _ => ErrorViewPresenter 146 | | } 147 | |} 148 | |""".stripMargin 149 | ) 150 | 151 | writeFile(rootViewScala)( 152 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 153 | | 154 | |import io.udash._ 155 | |import ${settings.rootPackage.mkPackage()}.RootState 156 | |import org.scalajs.dom.Element 157 | |import scalatags.JsDom.tags2.main$FrontendImportsPlaceholder 158 | | 159 | |object RootViewPresenter extends DefaultViewPresenterFactory[RootState.type](() => new RootView) 160 | | 161 | |class RootView extends View { 162 | | import ${settings.rootPackage.mkPackage()}.Context._ 163 | | import scalatags.JsDom.all._ 164 | | 165 | | private val child: Element = div().render 166 | | 167 | | private val content = div( 168 | | $FrontendStyledHeaderPlaceholder 169 | | main$FrontendStylesMainPlaceholder( 170 | | div$FrontendStylesBodyPlaceHolder( 171 | | h1("${settings.projectName}"), 172 | | child 173 | | ) 174 | | )$FrontendStyledFooterPlaceholder 175 | | ) 176 | | 177 | | override def getTemplate: Modifier = content 178 | | 179 | | override def renderChild(view: View): Unit = { 180 | | import io.udash.wrappers.jquery._ 181 | | jQ(child).children().remove() 182 | | view.getTemplate.applyTo(child) 183 | | } 184 | |} 185 | |""".stripMargin 186 | ) 187 | 188 | writeFile(indexViewScala)( 189 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 190 | | 191 | |import io.udash._ 192 | |import ${settings.rootPackage.mkPackage()}._ 193 | |import org.scalajs.dom.Element$FrontendImportsPlaceholder 194 | | 195 | |object IndexViewPresenter extends DefaultViewPresenterFactory[IndexState.type](() => new IndexView) 196 | | 197 | |class IndexView extends View { 198 | | import ${settings.rootPackage.mkPackage()}.Context._ 199 | | import scalatags.JsDom.all._ 200 | | 201 | | private val content = div( 202 | | h2("Thank you for choosing Udash! Take a look at following demo pages:"), 203 | | ul$FrontendStylesStepsListPlaceholder( 204 | | $FrontendIndexMenuPlaceholder 205 | | ), 206 | | h3("Read more"), 207 | | ul( 208 | | li( 209 | | a(${FrontendStylesLinkBlackPlaceholder}href := "http://udash.io/", target := "_blank")("Visit Udash Homepage.") 210 | | ), 211 | | li( 212 | | a(${FrontendStylesLinkBlackPlaceholder}href := "${settings.udashDevGuide}", target := "_blank")("Read more in Udash Guide.") 213 | | ), 214 | | li( 215 | | a(${FrontendStylesLinkBlackPlaceholder}href := "https://www.scala-js.org/", target := "_blank")("Read more about Scala.js.") 216 | | ), 217 | | li( 218 | | a(${FrontendStylesLinkBlackPlaceholder}href := "https://japgolly.github.io/scalacss/book/", target := "_blank")("Read more about ScalaCSS") 219 | | ), 220 | | li( 221 | | a(${FrontendStylesLinkBlackPlaceholder}href := "http://www.lihaoyi.com/scalatags/", target := "_blank")("Read more about ScalaTags") 222 | | ) 223 | | ) 224 | | ) 225 | | 226 | | override def getTemplate: Modifier = content 227 | | 228 | | override def renderChild(view: View): Unit = {} 229 | |} 230 | |""".stripMargin 231 | ) 232 | 233 | writeFile(errorViewScala)( 234 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 235 | | 236 | |import io.udash._ 237 | |import ${settings.rootPackage.mkPackage()}.IndexState 238 | |import org.scalajs.dom.Element 239 | | 240 | |object ErrorViewPresenter extends DefaultViewPresenterFactory[IndexState.type](() => new ErrorView) 241 | | 242 | |class ErrorView extends View { 243 | | import scalatags.JsDom.all._ 244 | | 245 | | private val content = h3( 246 | | "URL not found!" 247 | | ) 248 | | 249 | | override def getTemplate: Modifier = content 250 | | 251 | | override def renderChild(view: View): Unit = {} 252 | |} 253 | |""".stripMargin 254 | ) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/jetty/JettyLauncherPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.jetty 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.exceptions.InvalidConfiguration 6 | import io.udash.generator.plugins._ 7 | import io.udash.generator.plugins.sbt.{SBTModulesPlugin, SBTProjectFiles} 8 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 9 | import io.udash.generator.utils._ 10 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 11 | 12 | object JettyLauncherPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 13 | 14 | override val dependencies = Seq(SBTModulesPlugin) 15 | 16 | override def run(settings: GeneratorSettings): GeneratorSettings = { 17 | settings.projectType match { 18 | case FrontendOnlyProject => 19 | throw InvalidConfiguration("You can not add Jetty launcher into frontend only project.") 20 | case StandardProject(backend, _, frontend) => 21 | updateSBTConfig(settings, frontend) 22 | createJettyServer(rootPackageInSrc(settings.rootDirectory.subFile(backend), settings), settings, backend) 23 | } 24 | 25 | settings 26 | } 27 | 28 | private def updateSBTConfig(settings: GeneratorSettings, frontendModuleName: String): Unit = { 29 | val sbtConfigFile = buildSbt(settings) 30 | val sbtDepsFile = dependenciesScala(settings) 31 | val udashBuildFile = udashBuildScala(settings) 32 | 33 | requireFilesExist(Seq(sbtConfigFile, sbtDepsFile, udashBuildFile)) 34 | 35 | appendOnPlaceholder(sbtConfigFile)(RootSettingsPlaceholder, 36 | s""", 37 | | mainClass in Compile := Some("${settings.rootPackage.mkPackage()}.Launcher")""".stripMargin) 38 | 39 | appendOnPlaceholder(sbtConfigFile)(BackendSettingsPlaceholder, 40 | s""", 41 | | 42 | | compile := (compile in Compile).value, 43 | | (compile in Compile) := (compile in Compile).dependsOn(copyStatics).value, 44 | | copyStatics := IO.copyDirectory((crossTarget in $frontendModuleName).value / StaticFilesDir, (target in Compile).value / StaticFilesDir), 45 | | copyStatics := copyStatics.dependsOn(compileStatics in $frontendModuleName).value, 46 | | 47 | | mappings in (Compile, packageBin) ++= { 48 | | copyStatics.value 49 | | ((target in Compile).value / StaticFilesDir).***.get map { file => 50 | | file -> file.getAbsolutePath.stripPrefix((target in Compile).value.getAbsolutePath) 51 | | } 52 | | }, 53 | | 54 | | watchSources ++= (sourceDirectory in $frontendModuleName).value.***.get""".stripMargin) 55 | 56 | appendOnPlaceholder(sbtDepsFile)(DependenciesVariablesPlaceholder, 57 | s""" 58 | | val jettyVersion = "${settings.jettyVersion}"""".stripMargin) 59 | 60 | appendOnPlaceholder(sbtDepsFile)(DependenciesBackendPlaceholder, 61 | s""", 62 | | "org.eclipse.jetty" % "jetty-server" % jettyVersion, 63 | | "org.eclipse.jetty" % "jetty-servlet" % jettyVersion""".stripMargin) 64 | 65 | appendOnPlaceholder(udashBuildFile)(UdashBuildPlaceholder, 66 | s""" 67 | | val copyStatics = taskKey[Unit]("Copy frontend static files into backend target.")""".stripMargin) 68 | } 69 | 70 | private def createJettyServer(rootPackage: File, settings: GeneratorSettings, backendModuleName: String): Unit = { 71 | val resourcesDir = resources(settings.rootDirectory.subFile(backendModuleName)) 72 | val logbackXml = resourcesDir.subFile("logback.xml") 73 | 74 | val jettyDir = "jetty" 75 | val jettyPackage = rootPackage.subFile(jettyDir) 76 | val appServerScala = jettyPackage.subFile("ApplicationServer.scala") 77 | val launcherScala = rootPackage.subFile("Launcher.scala") 78 | 79 | requireFilesExist(Seq(rootPackage)) 80 | createDirs(Seq(jettyPackage, resourcesDir)) 81 | createFiles(Seq(appServerScala, launcherScala, logbackXml)) 82 | 83 | writeFile(appServerScala)( 84 | s"""package ${settings.rootPackage.mkPackage()}.$jettyDir 85 | | 86 | |import org.eclipse.jetty.server.Server 87 | |import org.eclipse.jetty.server.handler.gzip.GzipHandler 88 | |import org.eclipse.jetty.server.session.SessionHandler 89 | |import org.eclipse.jetty.servlet.{DefaultServlet, ServletContextHandler, ServletHolder} 90 | | 91 | |class ApplicationServer(val port: Int, resourceBase: String) { 92 | | private val server = new Server(port) 93 | | private val contextHandler = new ServletContextHandler 94 | | 95 | | contextHandler.setSessionHandler(new SessionHandler) 96 | | contextHandler.setGzipHandler(new GzipHandler) 97 | | server.setHandler(contextHandler) 98 | | 99 | | def start() = server.start() 100 | | 101 | | def stop() = server.stop() 102 | | 103 | | private val appHolder = { 104 | | val appHolder = new ServletHolder(new DefaultServlet) 105 | | appHolder.setAsyncSupported(true) 106 | | appHolder.setInitParameter("resourceBase", resourceBase) 107 | | appHolder 108 | | } 109 | | contextHandler.addServlet(appHolder, "/*")$BackendAppServerPlaceholder 110 | |} 111 | | 112 | """.stripMargin 113 | ) 114 | 115 | writeFile(logbackXml)( 116 | """ 117 | | 118 | | 119 | | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 120 | | 121 | | 122 | | 123 | | 124 | | 125 | | 126 | | logs/udash-guide-${bySecond}.log 127 | | true 128 | | 129 | | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 130 | | 131 | | 132 | | 133 | | 134 | | 135 | | 136 | | 137 | |""".stripMargin) 138 | 139 | writeFile(launcherScala)( 140 | s"""package ${settings.rootPackage.mkPackage()} 141 | | 142 | |import ${settings.rootPackage.mkPackage()}.$jettyDir.ApplicationServer 143 | | 144 | |object Launcher { 145 | | def main(args: Array[String]): Unit = { 146 | | val server = new ApplicationServer(8080, "$backendModuleName/target/UdashStatic/WebContent") 147 | | server.start() 148 | | } 149 | |} 150 | | 151 | """.stripMargin 152 | ) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/rpc/RPCDemosPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.rpc 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.exceptions.InvalidConfiguration 6 | import io.udash.generator.plugins._ 7 | import io.udash.generator.plugins.sbt.SBTProjectFiles 8 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 9 | import io.udash.generator.utils._ 10 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 11 | 12 | object RPCDemosPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 13 | 14 | override val dependencies = Seq(RPCPlugin) 15 | 16 | override def run(settings: GeneratorSettings): GeneratorSettings = { 17 | val rootPck: File = settings.projectType match { 18 | case FrontendOnlyProject => 19 | throw InvalidConfiguration("You can not add RPC into frontend only project.") 20 | case StandardProject(_, shared, frontend) => 21 | rootPackageInSrc(settings.rootDirectory.subFile(frontend), settings) 22 | } 23 | val stateName = createDemoView(rootPck, settings) 24 | addIndexLink(rootPck, stateName) 25 | 26 | settings 27 | } 28 | 29 | private def addIndexLink(rootPackage: File, state: String): Unit = { 30 | val indexViewScala = viewsPackageInSrc(rootPackage).subFile("IndexView.scala") 31 | requireFilesExist(Seq(indexViewScala)) 32 | 33 | appendOnPlaceholder(indexViewScala)(FrontendIndexMenuPlaceholder, 34 | s""", 35 | | li(a(${FrontendStylesLinkBlackPlaceholder}href := $state.url)("RPC demo"))""".stripMargin) 36 | } 37 | 38 | private def createDemoView(rootPackage: File, settings: GeneratorSettings): String = { 39 | val statesScala = rootPackage.subFile("states.scala") 40 | val routingRegistryDefScala = rootPackage.subFile("RoutingRegistryDef.scala") 41 | val statesToViewPresenterDefScala = rootPackage.subFile("StatesToViewPresenterDef.scala") 42 | 43 | requireFilesExist(Seq(viewsPackageInSrc(rootPackage), statesScala, routingRegistryDefScala, statesToViewPresenterDefScala)) 44 | 45 | val rpcDemoViewScala = viewsPackageInSrc(rootPackage).subFile("RPCDemoView.scala") 46 | val stateName = "RPCDemoState" 47 | 48 | appendOnPlaceholder(statesScala)(FrontendStatesRegistryPlaceholder, 49 | s""" 50 | | 51 | |case object $stateName extends RoutingState(RootState)""".stripMargin) 52 | 53 | appendOnPlaceholder(routingRegistryDefScala)(FrontendRoutingRegistryPlaceholder, 54 | s""" 55 | | case "/rpc" => $stateName""".stripMargin) 56 | 57 | appendOnPlaceholder(statesToViewPresenterDefScala)(FrontendVPRegistryPlaceholder, 58 | s""" 59 | | case $stateName => RPCDemoViewPresenter""".stripMargin) 60 | 61 | writeFile(rpcDemoViewScala)( 62 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 63 | | 64 | |import io.udash._ 65 | |import ${settings.rootPackage.mkPackage()}.$stateName 66 | |import org.scalajs.dom.Element$FrontendImportsPlaceholder 67 | | 68 | |import scala.util.{Success, Failure} 69 | | 70 | |case object RPCDemoViewPresenter extends DefaultViewPresenterFactory[$stateName.type](() => { 71 | | import ${settings.rootPackage.mkPackage()}.Context._ 72 | | 73 | | val serverResponse = Property[String]("???") 74 | | val input = Property[String]("") 75 | | input.listen((value: String) => { 76 | | serverRpc.hello(value).onComplete { 77 | | case Success(resp) => serverResponse.set(resp) 78 | | case Failure(_) => serverResponse.set("Error") 79 | | } 80 | | }) 81 | | 82 | | serverRpc.pushMe() 83 | | 84 | | new RPCDemoView(input, serverResponse) 85 | |}) 86 | | 87 | |class RPCDemoView(input: Property[String], serverResponse: Property[String]) extends View { 88 | | import scalatags.JsDom.all._ 89 | | 90 | | private val content = div( 91 | | h2( 92 | | "You can find this demo source code in: ", 93 | | i("${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.RPCDemoView") 94 | | ), 95 | | h3("Example"), 96 | | TextInput.debounced(input, placeholder := "Type your name..."), 97 | | p("Server response: ", bind(serverResponse)), 98 | | h3("Read more"), 99 | | a$FrontendStylesLinkBlackPlaceholder(href := "${settings.udashDevGuide}#/rpc", target := "_blank")("Read more in Udash Guide.") 100 | | ) 101 | | 102 | | override def getTemplate: Modifier = content 103 | | 104 | | override def renderChild(view: View): Unit = {} 105 | |} 106 | |""".stripMargin 107 | ) 108 | 109 | stateName 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/rpc/RPCPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.rpc 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.exceptions.InvalidConfiguration 6 | import io.udash.generator.plugins._ 7 | import io.udash.generator.plugins.jetty.JettyLauncherPlugin 8 | import io.udash.generator.plugins.sbt.SBTProjectFiles 9 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 10 | import io.udash.generator.utils._ 11 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 12 | 13 | object RPCPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 14 | 15 | override val dependencies = Seq(JettyLauncherPlugin) 16 | 17 | val rpcDir = "rpc" 18 | 19 | override def run(settings: GeneratorSettings): GeneratorSettings = { 20 | settings.projectType match { 21 | case FrontendOnlyProject => 22 | throw InvalidConfiguration("You can not add RPC into frontend only project.") 23 | case StandardProject(backend, shared, frontend) => 24 | updateSBTConfig(settings) 25 | createRPCInterfaces(rootPackageInSrc(settings.rootDirectory.subFile(shared), settings), settings) 26 | createBackendImplementation(rootPackageInSrc(settings.rootDirectory.subFile(backend), settings), settings) 27 | createFrontendImplementation(rootPackageInSrc(settings.rootDirectory.subFile(frontend), settings), settings) 28 | } 29 | 30 | settings 31 | } 32 | 33 | private def updateSBTConfig(settings: GeneratorSettings): Unit = { 34 | val sbtDepsFile = dependenciesScala(settings) 35 | 36 | requireFilesExist(Seq(sbtDepsFile)) 37 | 38 | appendOnPlaceholder(sbtDepsFile)(DependenciesFrontendPlaceholder, 39 | s""", 40 | | "io.udash" %%% "udash-rpc-frontend" % udashVersion""".stripMargin) 41 | 42 | appendOnPlaceholder(sbtDepsFile)(DependenciesCrossPlaceholder, 43 | s""", 44 | | "io.udash" %%% "udash-rpc-shared" % udashVersion""".stripMargin) 45 | 46 | appendOnPlaceholder(sbtDepsFile)(DependenciesBackendPlaceholder, 47 | s""", 48 | | "io.udash" %% "udash-rpc-backend" % udashVersion, 49 | | "org.eclipse.jetty.websocket" % "websocket-server" % jettyVersion""".stripMargin) 50 | } 51 | 52 | private def createRPCInterfaces(rootPackage: File, settings: GeneratorSettings): Unit = { 53 | val rpcPackage = rootPackage.subFile(rpcDir) 54 | val clientRPCScala = rpcPackage.subFile("MainClientRPC.scala") 55 | val serverRPCScala = rpcPackage.subFile("MainServerRPC.scala") 56 | 57 | requireFilesExist(Seq(rootPackage)) 58 | createDirs(Seq(rpcPackage)) 59 | createFiles(Seq(clientRPCScala, serverRPCScala)) 60 | 61 | writeFile(clientRPCScala)( 62 | s"""package ${settings.rootPackage.mkPackage()}.$rpcDir 63 | | 64 | |import com.avsystem.commons.rpc.RPC 65 | |import io.udash.rpc._ 66 | | 67 | |@RPC 68 | |trait MainClientRPC { 69 | | def push(number: Int): Unit 70 | |} 71 | """.stripMargin 72 | ) 73 | 74 | writeFile(serverRPCScala)( 75 | s"""package ${settings.rootPackage.mkPackage()}.$rpcDir 76 | | 77 | |import com.avsystem.commons.rpc.RPC 78 | |import io.udash.rpc._ 79 | |import scala.concurrent.Future 80 | | 81 | |@RPC 82 | |trait MainServerRPC { 83 | | def hello(name: String): Future[String] 84 | | def pushMe(): Unit 85 | |} 86 | | 87 | """.stripMargin 88 | ) 89 | } 90 | 91 | private def createBackendImplementation(rootPackage: File, settings: GeneratorSettings): Unit = { 92 | val jettyDir = "jetty" 93 | val jettyPackage = rootPackage.subFile(jettyDir) 94 | val rpcPackage = rootPackage.subFile(rpcDir) 95 | val appServerScala = jettyPackage.subFile("ApplicationServer.scala") 96 | val exposedRpcInterfacesScala = rpcPackage.subFile("ExposedRpcInterfaces.scala") 97 | val clientRPCScala = rpcPackage.subFile("ClientRPC.scala") 98 | 99 | requireFilesExist(Seq(rootPackage, jettyPackage, appServerScala)) 100 | createDirs(Seq(rpcPackage)) 101 | createFiles(Seq(clientRPCScala, exposedRpcInterfacesScala)) 102 | 103 | writeFile(clientRPCScala)( 104 | s"""package ${settings.rootPackage.mkPackage()}.$rpcDir 105 | | 106 | |import io.udash.rpc._ 107 | | 108 | |import scala.concurrent.ExecutionContext 109 | | 110 | |object ClientRPC { 111 | | def apply(target: ClientRPCTarget)(implicit ec: ExecutionContext): MainClientRPC = { 112 | | new DefaultClientRPC[MainClientRPC](target).get 113 | | } 114 | |} 115 | | 116 | """.stripMargin 117 | ) 118 | 119 | writeFile(exposedRpcInterfacesScala)( 120 | s"""package ${settings.rootPackage.mkPackage()}.$rpcDir 121 | | 122 | |import io.udash.rpc._ 123 | | 124 | |import scala.concurrent.Future 125 | |import scala.concurrent.ExecutionContext.Implicits.global 126 | | 127 | |class ExposedRpcInterfaces(implicit clientId: ClientId) extends MainServerRPC { 128 | | override def hello(name: String): Future[String] = 129 | | Future.successful(s"Hello, ${"$name"}!") 130 | | 131 | | override def pushMe(): Unit = 132 | | ClientRPC(clientId).push(42) 133 | |} 134 | | 135 | """.stripMargin 136 | ) 137 | 138 | appendOnPlaceholder(appServerScala)(BackendAppServerPlaceholder, 139 | s""" 140 | | 141 | | private val atmosphereHolder = { 142 | | import io.udash.rpc._ 143 | | import ${settings.rootPackage.mkPackage()}.$rpcDir._ 144 | | import scala.concurrent.ExecutionContext.Implicits.global 145 | | 146 | | val config = new DefaultAtmosphereServiceConfig[MainServerRPC]((clientId) => new DefaultExposesServerRPC[MainServerRPC](new ExposedRpcInterfaces()(clientId))) 147 | | val framework = new DefaultAtmosphereFramework(config) 148 | | 149 | | //Disabling all files scan during service auto-configuration, 150 | | //as it's quite time-consuming - a few seconds long. 151 | | // 152 | | //If it's really required, enable it, but at the cost of start-up overhead or some tuning has to be made. 153 | | //For that purpose, check what is going on in: 154 | | //- DefaultAnnotationProcessor 155 | | //- org.atmosphere.cpr.AtmosphereFramework.autoConfigureService 156 | | framework.allowAllClassesScan(false) 157 | | 158 | | framework.init() 159 | | 160 | | val atmosphereHolder = new ServletHolder(new RpcServlet(framework)) 161 | | atmosphereHolder.setAsyncSupported(true) 162 | | atmosphereHolder 163 | | } 164 | | contextHandler.addServlet(atmosphereHolder, "/atm/*") 165 | """.stripMargin 166 | ) 167 | } 168 | 169 | private def createFrontendImplementation(rootPackage: File, settings: GeneratorSettings): Unit = { 170 | val rpcPackage = rootPackage.subFile(rpcDir) 171 | val rpcServiceScala = rpcPackage.subFile("RPCService.scala") 172 | val initScala = rootPackage.subFile("init.scala") 173 | 174 | requireFilesExist(Seq(rootPackage, initScala)) 175 | createDirs(Seq(rpcPackage)) 176 | createFiles(Seq(rpcServiceScala)) 177 | 178 | writeFile(rpcServiceScala)( 179 | s"""package ${settings.rootPackage.mkPackage()}.$rpcDir 180 | | 181 | |class RPCService extends MainClientRPC { 182 | | override def push(number: Int): Unit = 183 | | println(s"Push from server: ${"$number"}") 184 | |} 185 | | 186 | """.stripMargin 187 | ) 188 | 189 | appendOnPlaceholder(initScala)(FrontendContextPlaceholder, 190 | s""" 191 | | 192 | | import io.udash.rpc._ 193 | | import ${settings.rootPackage.mkPackage()}.$rpcDir._ 194 | | val serverRpc = DefaultServerRPC[MainClientRPC, MainServerRPC](new RPCService) 195 | """.stripMargin 196 | ) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/sbt/SBTBootstrapPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.sbt 2 | 3 | import io.udash.generator.plugins.{DependenciesPlaceholder, UdashBuildPlaceholder} 4 | import io.udash.generator.utils._ 5 | import io.udash.generator.{GeneratorPlugin, GeneratorSettings} 6 | 7 | /** 8 | * Creates basic SBT project:
9 | * * build.sbt with basic project settings
10 | * * project/build.properties with SBT version
11 | * * project/plugins.sbt with ScalaJS plugin
12 | * * project/UdashBuild.scala with custom tasks
13 | * * project/Dependencies.scala with dependencies
14 | */ 15 | object SBTBootstrapPlugin extends GeneratorPlugin with SBTProjectFiles { 16 | override def run(settings: GeneratorSettings): GeneratorSettings = { 17 | createDirs(Seq(projectDir(settings)), requireNotExists = true) 18 | createFiles(Seq(buildSbt(settings), buildProperties(settings), pluginsSbt(settings), 19 | udashBuildScala(settings), dependenciesScala(settings))) 20 | 21 | val importJsWorkbench = if (settings.shouldEnableJsWorkbench) "import com.lihaoyi.workbench.Plugin._" else "" 22 | val addJsWorkbenchPlugin = if (settings.shouldEnableJsWorkbench) s"""addSbtPlugin("com.lihaoyi" % "workbench" % "0.2.3")""" else "" 23 | 24 | writeFile(buildSbt(settings))( 25 | s"""$importJsWorkbench 26 | |import UdashBuild._ 27 | |import Dependencies._ 28 | | 29 | |name := "${settings.projectName}" 30 | | 31 | |version in ThisBuild := "0.1.0-SNAPSHOT" 32 | |scalaVersion in ThisBuild := "${settings.scalaVersion}" 33 | |organization in ThisBuild := "${settings.organization}" 34 | |crossPaths in ThisBuild := false 35 | |scalacOptions in ThisBuild ++= Seq( 36 | | "-feature", 37 | | "-deprecation", 38 | | "-unchecked", 39 | | "-language:implicitConversions", 40 | | "-language:existentials", 41 | | "-language:dynamics", 42 | | "-Xfuture", 43 | | "-Xfatal-warnings", 44 | | "-Xlint:-unused,_" 45 | |) 46 | | 47 | |""".stripMargin 48 | ) 49 | 50 | writeFile(buildProperties(settings))( 51 | s"sbt.version = ${settings.sbtVersion}") 52 | 53 | writeFile(pluginsSbt(settings))( 54 | s"""logLevel := Level.Warn 55 | |addSbtPlugin("org.scala-js" % "sbt-scalajs" % "${settings.scalaJSVersion}") 56 | | 57 | |$addJsWorkbenchPlugin 58 | | 59 | |""".stripMargin) 60 | 61 | writeFile(udashBuildScala(settings))( 62 | s"""import org.scalajs.sbtplugin.ScalaJSPlugin.AutoImport._ 63 | |import sbt.Keys._ 64 | |import sbt._ 65 | | 66 | |object UdashBuild {$UdashBuildPlaceholder} 67 | | 68 | |""".stripMargin) 69 | 70 | writeFile(dependenciesScala(settings))( 71 | s"""import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 72 | |import sbt._ 73 | | 74 | |object Dependencies {$DependenciesPlaceholder} 75 | | 76 | |""".stripMargin) 77 | 78 | settings 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/sbt/SBTModulesPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.sbt 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.exceptions.FileCreationError 6 | import io.udash.generator.plugins._ 7 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 8 | import io.udash.generator.utils._ 9 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 10 | 11 | /** 12 | * Prepares SBT modules configuration. 13 | */ 14 | object SBTModulesPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 15 | 16 | override val dependencies = Seq(SBTBootstrapPlugin) 17 | 18 | override def run(settings: GeneratorSettings): GeneratorSettings = { 19 | settings.projectType match { 20 | case FrontendOnlyProject => 21 | generateFrontendOnlyProject(settings) 22 | case StandardProject(backend, shared, frontend) => 23 | generateStandardProject( 24 | settings.rootDirectory.subFile(backend), 25 | settings.rootDirectory.subFile(shared), 26 | settings.rootDirectory.subFile(frontend), settings) 27 | } 28 | 29 | settings 30 | } 31 | 32 | private def scalajsWorkbenchSettings(settings: GeneratorSettings) = 33 | if (settings.shouldEnableJsWorkbench) 34 | s""".settings(workbenchSettings:_*) 35 | | .settings( 36 | | bootSnippet := "${settings.rootPackage.mkPackage()}.Init().main();", 37 | | updatedJS := { 38 | | var files: List[String] = Nil 39 | | ((crossTarget in Compile).value / StaticFilesDir ** "*.js").get.foreach { 40 | | (x: File) => 41 | | streams.value.log.info("workbench: Checking " + x.getName) 42 | | FileFunction.cached(streams.value.cacheDirectory / x.getName, FilesInfo.lastModified, FilesInfo.lastModified) { 43 | | (f: Set[File]) => 44 | | val fsPath = f.head.getAbsolutePath.drop(new File("").getAbsolutePath.length) 45 | | files = "http://localhost:12345" + fsPath :: files 46 | | f 47 | | }(Set(x)) 48 | | } 49 | | files 50 | | }, 51 | | //// use either refreshBrowsers OR updateBrowsers 52 | | // refreshBrowsers := (refreshBrowsers triggeredBy (compileStatics in Compile)).value 53 | | updateBrowsers := (updateBrowsers triggeredBy (compileStatics in Compile)).value 54 | | ) 55 | |""".stripMargin 56 | else "" 57 | 58 | /** 59 | * Creates modules dirs:
60 | * * src/main/assets
61 | * * src/main/assets/fonts
62 | * * src/main/assets/images
63 | * * src/main/assets/index.dev.html
64 | * * src/main/assets/index.prod.html
65 | * * src/main/scala/{rootPackage}
66 | * * src/test/scala/{rootPackage}
67 | * and appends `build.sbt` modules config with dependencies config in `project/Dependencies.scala`. 68 | */ 69 | private def generateFrontendOnlyProject(settings: GeneratorSettings): Unit = { 70 | createModulesDirs(Seq(settings.rootDirectory), settings) 71 | createFrontendExtraDirs(settings.rootDirectory, settings, Option.empty) 72 | 73 | requireFilesExist(Seq(buildSbt(settings), projectDir(settings), udashBuildScala(settings), dependenciesScala(settings))) 74 | generateFrontendTasks(udashBuildScala(settings), indexDevHtml(settings.rootDirectory), indexProdHtml(settings.rootDirectory)) 75 | 76 | val frontendModuleName = wrapValName(settings.projectName) 77 | val depsName = wrapValName("deps") 78 | val depsJSName = wrapValName("depsJS") 79 | 80 | appendFile(buildSbt(settings))( 81 | s"""val $frontendModuleName = project.in(file(".")).enablePlugins(ScalaJSPlugin) 82 | | .settings( 83 | | libraryDependencies ++= $depsName.value, 84 | | jsDependencies ++= $depsJSName.value, 85 | | scalaJSUseMainModuleInitializer in Compile := true, 86 | | 87 | | compile := (compile in Compile).dependsOn(compileStatics).value, 88 | | compileStatics := { 89 | | IO.copyDirectory(sourceDirectory.value / "main/assets/fonts", crossTarget.value / StaticFilesDir / WebContent / "assets/fonts") 90 | | IO.copyDirectory(sourceDirectory.value / "main/assets/images", crossTarget.value / StaticFilesDir / WebContent / "assets/images") 91 | | val statics = compileStaticsForRelease.value 92 | | (crossTarget.value / StaticFilesDir).***.get 93 | | }, 94 | | 95 | | artifactPath in(Compile, fastOptJS) := 96 | | (crossTarget in(Compile, fastOptJS)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendImplFastJs}", 97 | | artifactPath in(Compile, fullOptJS) := 98 | | (crossTarget in(Compile, fullOptJS)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendImplJs}", 99 | | artifactPath in(Compile, packageJSDependencies) := 100 | | (crossTarget in(Compile, packageJSDependencies)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendDepsFastJs}", 101 | | artifactPath in(Compile, packageMinifiedJSDependencies) := 102 | | (crossTarget in(Compile, packageMinifiedJSDependencies)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendDepsJs}" 103 | | )${scalajsWorkbenchSettings(settings)} 104 | | $FrontendModulePlaceholder 105 | | 106 | |""".stripMargin) 107 | 108 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesPlaceholder, 109 | s""" 110 | | $DependenciesVariablesPlaceholder 111 | | 112 | | val $depsName = Def.setting(Seq[ModuleID]($DependenciesFrontendPlaceholder 113 | | )) 114 | | 115 | | val $depsJSName = Def.setting(Seq[org.scalajs.sbtplugin.JSModuleID]($DependenciesFrontendJSPlaceholder 116 | | )) 117 | |""".stripMargin 118 | ) 119 | } 120 | 121 | /** 122 | * Creates modules dirs:
123 | * * {module}/src/main/scala/{rootPackage}
124 | * * {module}/src/test/scala/{rootPackage}
125 | * extra in frontend:
126 | * * {module}/src/main/assets
127 | * * {module}/src/main/assets/fonts
128 | * * {module}/src/main/assets/images
129 | * * {module}/src/main/assets/index.dev.html
130 | * * {module}/src/main/assets/index.prod.html
131 | * and appends `build.sbt` modules config with dependencies config in `project/Dependencies.scala`. 132 | */ 133 | private def generateStandardProject(backend: File, shared: File, frontend: File, settings: GeneratorSettings): Unit = { 134 | createModulesDirs(Seq(backend, shared, frontend), settings) 135 | createFrontendExtraDirs(frontend, settings, Option(frontend.getName)) 136 | 137 | requireFilesExist(Seq(buildSbt(settings), projectDir(settings), udashBuildScala(settings), dependenciesScala(settings))) 138 | generateFrontendTasks(udashBuildScala(settings), indexDevHtml(frontend), indexProdHtml(frontend)) 139 | 140 | val rootModuleName = wrapValName(settings.projectName) 141 | val backendModuleName = wrapValName(backend.getName) 142 | val frontendModuleName = wrapValName(frontend.getName) 143 | val sharedModuleName = wrapValName(shared.getName) 144 | val sharedJSModuleName = wrapValName(shared.getName + "JS") 145 | val sharedJVMModuleName = wrapValName(shared.getName + "JVM") 146 | 147 | val crossDepsName = wrapValName("crossDeps") 148 | val backendDepsName = wrapValName("backendDeps") 149 | val frontendDepsName = wrapValName("frontendDeps") 150 | val frontendJSDepsName = wrapValName("frontendJSDeps") 151 | 152 | appendFile(buildSbt(settings))( 153 | s"""def crossLibs(configuration: Configuration) = 154 | | libraryDependencies ++= $crossDepsName.value.map(_ % configuration) 155 | | 156 | |lazy val $rootModuleName = project.in(file(".")) 157 | | .aggregate($sharedJSModuleName, $sharedJVMModuleName, $frontendModuleName, $backendModuleName) 158 | | .dependsOn($backendModuleName) 159 | | .settings( 160 | | publishArtifact := false$RootSettingsPlaceholder 161 | | )$RootModulePlaceholder 162 | | 163 | |lazy val $sharedModuleName = crossProject.crossType(CrossType.Pure).in(file("${shared.getName}")) 164 | | .settings( 165 | | crossLibs(Provided)$SharedSettingsPlaceholder 166 | | )$SharedModulePlaceholder 167 | | 168 | |lazy val $sharedJVMModuleName = $sharedModuleName.jvm$SharedJVMModulePlaceholder 169 | |lazy val $sharedJSModuleName = $sharedModuleName.js$SharedJSModulePlaceholder 170 | | 171 | |lazy val $backendModuleName = project.in(file("${backend.getName}")) 172 | | .dependsOn($sharedJVMModuleName) 173 | | .settings( 174 | | libraryDependencies ++= $backendDepsName.value, 175 | | crossLibs(Compile)$BackendSettingsPlaceholder 176 | | )$BackendModulePlaceholder 177 | | 178 | |lazy val $frontendModuleName = project.in(file("${frontend.getName}")).enablePlugins(ScalaJSPlugin) 179 | | .dependsOn($sharedJSModuleName) 180 | | .settings( 181 | | libraryDependencies ++= $frontendDepsName.value, 182 | | crossLibs(Compile), 183 | | jsDependencies ++= $frontendJSDepsName.value, 184 | | scalaJSUseMainModuleInitializer in Compile := true, 185 | | 186 | | compile := (compile in Compile).dependsOn(compileStatics).value, 187 | | compileStatics := { 188 | | IO.copyDirectory(sourceDirectory.value / "main/assets/fonts", crossTarget.value / StaticFilesDir / WebContent / "assets/fonts") 189 | | IO.copyDirectory(sourceDirectory.value / "main/assets/images", crossTarget.value / StaticFilesDir / WebContent / "assets/images") 190 | | val statics = compileStaticsForRelease.value 191 | | (crossTarget.value / StaticFilesDir).***.get 192 | | }, 193 | | 194 | | artifactPath in(Compile, fastOptJS) := 195 | | (crossTarget in(Compile, fastOptJS)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendImplFastJs}", 196 | | artifactPath in(Compile, fullOptJS) := 197 | | (crossTarget in(Compile, fullOptJS)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendImplJs}", 198 | | artifactPath in(Compile, packageJSDependencies) := 199 | | (crossTarget in(Compile, packageJSDependencies)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendDepsFastJs}", 200 | | artifactPath in(Compile, packageMinifiedJSDependencies) := 201 | | (crossTarget in(Compile, packageMinifiedJSDependencies)).value / StaticFilesDir / WebContent / "scripts" / "${settings.frontendDepsJs}" 202 | | )${scalajsWorkbenchSettings(settings)} 203 | | $FrontendModulePlaceholder 204 | | 205 | |""".stripMargin) 206 | 207 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesPlaceholder, 208 | s""" 209 | | $DependenciesVariablesPlaceholder 210 | | 211 | | val $crossDepsName = Def.setting(Seq[ModuleID]($DependenciesCrossPlaceholder 212 | | )) 213 | | 214 | | val $frontendDepsName = Def.setting(Seq[ModuleID]($DependenciesFrontendPlaceholder 215 | | )) 216 | | 217 | | val $frontendJSDepsName = Def.setting(Seq[org.scalajs.sbtplugin.JSModuleID]($DependenciesFrontendJSPlaceholder 218 | | )) 219 | | 220 | | val $backendDepsName = Def.setting(Seq[ModuleID]($DependenciesBackendPlaceholder 221 | | )) 222 | |""".stripMargin 223 | ) 224 | } 225 | 226 | private def createModulesDirs(modules: Seq[File], settings: GeneratorSettings): Unit = { 227 | modules.foreach((modulePath: File) => { 228 | val module = modulePath 229 | if (modulePath != settings.rootDirectory && !module.mkdir()) throw FileCreationError(module.toString) 230 | createDirs(Seq(rootPackageInSrc(module, settings), rootPackageInTestSrc(module, settings))) 231 | }) 232 | } 233 | 234 | private def createFrontendExtraDirs(frontend: File, settings: GeneratorSettings, frontendModuleName: Option[String]): Unit = { 235 | createDirs(Seq(images(frontend), fonts(frontend))) 236 | 237 | val indexDev: File = indexDevHtml(frontend) 238 | val indexProd: File = indexProdHtml(frontend) 239 | 240 | val frontendDirectoryName = frontendModuleName match { 241 | case None => "" 242 | case Some(name) => name + "/" 243 | } 244 | 245 | val scripts = 246 | if (settings.shouldEnableJsWorkbench) 247 | s""" 248 | | 249 | | 250 | | 251 | """.stripMargin 252 | else 253 | s""" 254 | | 255 | | 256 | """.stripMargin 257 | 258 | 259 | createFiles(Seq(indexDev, indexProd), requireNotExists = true) 260 | 261 | writeFile(indexDev)( 262 | s""" 263 | | 264 | | 265 | | 266 | | ${settings.projectName} - development 267 | | 268 | | $scripts 269 | | 270 | | $HTMLHeadPlaceholder 271 | | 272 | | 273 | |
274 | | 275 | | 276 | |""".stripMargin) 277 | 278 | writeFile(indexProd)( 279 | s""" 280 | | 281 | | 282 | | 283 | | ${settings.projectName} 284 | | 285 | | 286 | | 287 | | $HTMLHeadPlaceholder 288 | | 289 | | 290 | |
291 | | 292 | | 293 | |""".stripMargin) 294 | } 295 | 296 | private def generateFrontendTasks(udashBuildScala: File, indexDevHtml: File, indexProdHtml: File): Unit = { 297 | appendOnPlaceholder(udashBuildScala)(UdashBuildPlaceholder, 298 | s""" 299 | | val StaticFilesDir = "UdashStatic" 300 | | val WebContent = "WebContent" 301 | | 302 | | def copyIndex(file: File, to: File) = { 303 | | val newFile = Path(to.toPath.toString + "/index.html") 304 | | IO.copyFile(file, newFile.asFile) 305 | | } 306 | | 307 | | val compileStatics = taskKey[Seq[File]]("Frontend static files manager.") 308 | | 309 | | val compileStaticsForRelease = Def.taskDyn { 310 | | val outDir = crossTarget.value / StaticFilesDir / WebContent 311 | | if (!isSnapshot.value) { 312 | | Def.task { 313 | | val indexFile = sourceDirectory.value / "main/assets/${indexProdHtml.getName}" 314 | | copyIndex(indexFile, outDir) 315 | | (fullOptJS in Compile).value 316 | | (packageMinifiedJSDependencies in Compile).value 317 | | } 318 | | } else { 319 | | Def.task { 320 | | val indexFile = sourceDirectory.value / "main/assets/${indexDevHtml.getName}" 321 | | copyIndex(indexFile, outDir) 322 | | (fastOptJS in Compile).value 323 | | (packageJSDependencies in Compile).value 324 | | } 325 | | } 326 | | } 327 | |""".stripMargin) 328 | } 329 | 330 | //TODO: wrap only when its necessary 331 | private def wrapValName(name: String): String = 332 | if (name.contains("-")) s"`$name`" 333 | else name 334 | } 335 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/sbt/SBTProjectFiles.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.sbt 2 | 3 | import io.udash.generator.GeneratorSettings 4 | import io.udash.generator.utils._ 5 | 6 | trait SBTProjectFiles { 7 | def buildSbt(settings: GeneratorSettings) = settings.rootDirectory.subFile("build.sbt") 8 | def projectDir(settings: GeneratorSettings) = settings.rootDirectory.subFile("project") 9 | def buildProperties(settings: GeneratorSettings) = projectDir(settings).subFile("build.properties") 10 | def pluginsSbt(settings: GeneratorSettings) = projectDir(settings).subFile("plugins.sbt") 11 | def udashBuildScala(settings: GeneratorSettings) = projectDir(settings).subFile("UdashBuild.scala") 12 | def dependenciesScala(settings: GeneratorSettings) = projectDir(settings).subFile("Dependencies.scala") 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/scalacss/ScalaCSSDemosPlugin.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.scalacss 2 | 3 | import java.io.File 4 | import javax.imageio.ImageIO 5 | 6 | import io.udash.generator.plugins._ 7 | import io.udash.generator.plugins.core.CoreDemosPlugin 8 | import io.udash.generator.plugins.rpc.RPCDemosPlugin 9 | import io.udash.generator.plugins.sbt.SBTProjectFiles 10 | import io.udash.generator.plugins.utils.{FrontendPaths, UtilPaths} 11 | import io.udash.generator.utils._ 12 | import io.udash.generator.{FrontendOnlyProject, GeneratorPlugin, GeneratorSettings, StandardProject} 13 | 14 | object ScalaCSSDemosPlugin extends GeneratorPlugin with SBTProjectFiles with FrontendPaths with UtilPaths { 15 | 16 | val stateName = "DemoStylesState" 17 | override val dependencies = Seq(RPCDemosPlugin, CoreDemosPlugin) 18 | 19 | override def run(settings: GeneratorSettings): GeneratorSettings = { 20 | var rootPck: File = null 21 | var frontendDir: File = null 22 | var imagesDir: File = null 23 | 24 | settings.projectType match { 25 | case FrontendOnlyProject => 26 | rootPck = rootPackageInSrc(settings.rootDirectory, settings) 27 | frontendDir = settings.rootDirectory 28 | imagesDir = images(settings.rootDirectory) 29 | case StandardProject(_, shared, frontend) => 30 | rootPck = rootPackageInSrc(settings.rootDirectory.subFile(frontend), settings) 31 | frontendDir = settings.rootDirectory.subFile(frontend) 32 | imagesDir = images(settings.rootDirectory.subFile(frontend)) 33 | } 34 | addIndexLink(rootPck, stateName) 35 | createDemoStyles(rootPck, settings) 36 | 37 | createImages(imagesDir, settings) 38 | prepareHtml(frontendDir, settings) 39 | 40 | settings 41 | } 42 | 43 | private def addIndexLink(rootPackage: File, state: String): Unit = { 44 | val indexViewScala = viewsPackageInSrc(rootPackage).subFile("IndexView.scala") 45 | requireFilesExist(Seq(indexViewScala)) 46 | 47 | appendOnPlaceholder(indexViewScala)(FrontendIndexMenuPlaceholder, 48 | s""", 49 | | li(a(${FrontendStylesLinkBlackPlaceholder}href := $state.url)("ScalaCSS demo view"))""".stripMargin) 50 | } 51 | 52 | private def prepareHtml(frontendDirectory: File, settings: GeneratorSettings): Unit = { 53 | val indexDev: File = indexDevHtml(frontendDirectory) 54 | val indexProd: File = indexProdHtml(frontendDirectory) 55 | 56 | requireFilesExist(Seq(indexDev, indexProd)) 57 | 58 | appendOnPlaceholder(indexDev)(HTMLHeadPlaceholder, 59 | s""" 60 | | 61 | | 62 | | 63 | | 64 | |""".stripMargin) 65 | 66 | appendOnPlaceholder(indexProd)(HTMLHeadPlaceholder, 67 | s""" 68 | | 69 | | 70 | | 71 | | 72 | |""".stripMargin) 73 | 74 | } 75 | 76 | private def createImages(imagesDirectory: File, settings: GeneratorSettings): Unit = { 77 | createDirs(Seq(imagesDirectory)) 78 | 79 | settings.assetsImages.foreach(filename => { 80 | val src = getClass.getResource(settings.imageResourcePath + filename) 81 | val target = imagesDirectory.subFile(filename) 82 | 83 | val image = ImageIO.read(src) 84 | ImageIO.write(image, filename.split("\\.").last, target) 85 | }) 86 | } 87 | 88 | private def createDemoStyles(rootPackage: File, settings: GeneratorSettings): Unit = { 89 | val globalStylesScala = stylesPackageInSrc(rootPackage).subFile("GlobalStyles.scala") 90 | val demoStylesScala = stylesPackageInSrc(rootPackage).subFile("DemoStyles.scala") 91 | 92 | val stylesConstantsPackage = stylesPackageInSrc(rootPackage).subFile("constants") 93 | val stylesConstantsScala = stylesConstantsPackage.subFile("StyleConstants.scala") 94 | 95 | val stylesFontsPackage = stylesPackageInSrc(rootPackage).subFile("fonts") 96 | val stylesFontsScala = stylesFontsPackage.subFile("UdashFonts.scala") 97 | 98 | val stylesPartialsPackage = stylesPackageInSrc(rootPackage).subFile("partials") 99 | val stylesHeaderScala = stylesPartialsPackage.subFile("Header.scala") 100 | val stylesFooterScala = stylesPartialsPackage.subFile("FooterStyles.scala") 101 | 102 | val stylesUtilsPackage = stylesPackageInSrc(rootPackage).subFile("utils") 103 | val stylesMediaQueriesScala = stylesUtilsPackage.subFile("MediaQueries.scala") 104 | val stylesStyleUtilsScala = stylesUtilsPackage.subFile("StyleUtils.scala") 105 | 106 | val configPackage = rootPackage.subFile("config") 107 | val configScala = configPackage.subFile("ExternalUrls.scala") 108 | 109 | val initScala = rootPackage.subFile("init.scala") 110 | 111 | val statesScala = rootPackage.subFile("states.scala") 112 | val rootViewScala = viewsPackageInSrc(rootPackage).subFile("RootView.scala") 113 | val indexViewScala = viewsPackageInSrc(rootPackage).subFile("IndexView.scala") 114 | val bindingDemoViewScala = viewsPackageInSrc(rootPackage).subFile("BindingDemoView.scala") 115 | val routingRegistryDefScala = rootPackage.subFile("RoutingRegistryDef.scala") 116 | val statesToViewPresenterDefScala = rootPackage.subFile("StatesToViewPresenterDef.scala") 117 | val rpcDemoViewScala = viewsPackageInSrc(rootPackage).subFile("RPCDemoView.scala") 118 | 119 | val demoStylesViewScala = viewsPackageInSrc(rootPackage).subFile("DemoStylesView.scala") 120 | 121 | val componentsPackage = viewsPackageInSrc(rootPackage).subFile("components") 122 | val imageFactoryScala = componentsPackage.subFile("ImageFactory.scala") 123 | val headerScala = componentsPackage.subFile("Header.scala") 124 | val footerScala = componentsPackage.subFile("Footer.scala") 125 | 126 | createDirs(Seq(stylesPackageInSrc(rootPackage), configPackage, componentsPackage, stylesConstantsPackage, stylesFontsPackage, stylesPartialsPackage, stylesUtilsPackage)) 127 | createFiles(Seq(globalStylesScala, demoStylesScala, demoStylesViewScala, configScala, imageFactoryScala, headerScala, footerScala, stylesConstantsScala, stylesFontsScala, stylesHeaderScala, stylesFooterScala, stylesMediaQueriesScala, stylesStyleUtilsScala), requireNotExists = true) 128 | requireFilesExist(Seq(dependenciesScala(settings), initScala, statesScala, routingRegistryDefScala, statesToViewPresenterDefScala, bindingDemoViewScala, indexViewScala, rootViewScala)) 129 | 130 | appendOnPlaceholder(dependenciesScala(settings))(DependenciesFrontendPlaceholder, 131 | s""", 132 | | "com.github.japgolly.scalacss" %%% "core" % "${settings.scalaCSSVersion}", 133 | | "com.github.japgolly.scalacss" %%% "ext-scalatags" % "${settings.scalaCSSVersion}"""".stripMargin) 134 | 135 | appendOnPlaceholder(statesScala)(FrontendStatesRegistryPlaceholder, 136 | s""" 137 | | 138 | |case object $stateName extends RoutingState(RootState)""".stripMargin) 139 | 140 | appendOnPlaceholder(routingRegistryDefScala)(FrontendRoutingRegistryPlaceholder, 141 | s""" 142 | | case "/scalacss" => $stateName""".stripMargin) 143 | 144 | appendOnPlaceholder(statesToViewPresenterDefScala)(FrontendVPRegistryPlaceholder, 145 | s""" 146 | | case $stateName => DemoStylesViewPresenter""".stripMargin) 147 | 148 | appendOnPlaceholder(bindingDemoViewScala)(FrontendImportsPlaceholder, 149 | s""" 150 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.DemoStyles 151 | |import scalacss.ScalatagsCss._""".stripMargin) 152 | 153 | appendOnPlaceholder(bindingDemoViewScala)(FrontendStylesLinkBlackPlaceholder, 154 | s"""(DemoStyles.underlineLinkBlack)""".stripMargin) 155 | 156 | appendOnPlaceholder(rootViewScala)(FrontendImportsPlaceholder, 157 | s""" 158 | |import ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.${componentsPackage.getName}.{Footer, Header} 159 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.{DemoStyles, GlobalStyles} 160 | |import scalacss.ScalatagsCss._""".stripMargin) 161 | 162 | appendOnPlaceholder(rootViewScala)(FrontendStyledHeaderPlaceholder, 163 | s"""Header.getTemplate,""".stripMargin) 164 | 165 | appendOnPlaceholder(rootViewScala)(FrontendStyledFooterPlaceholder, 166 | s""" 167 | |,Footer.getTemplate""".stripMargin) 168 | 169 | appendOnPlaceholder(rootViewScala)(FrontendStylesMainPlaceholder, 170 | s"""(GlobalStyles.main)""".stripMargin) 171 | 172 | appendOnPlaceholder(rootViewScala)(FrontendStylesBodyPlaceHolder, 173 | s"""(GlobalStyles.body)""".stripMargin) 174 | 175 | appendOnPlaceholder(rootViewScala)(FrontendStylesLinkBlackPlaceholder, 176 | s"""(DemoStyles.underlineLinkBlack)""".stripMargin) 177 | 178 | appendOnPlaceholder(indexViewScala)(FrontendStylesStepsListPlaceholder, 179 | s"""(DemoStyles.stepsList)""".stripMargin) 180 | 181 | appendOnPlaceholder(indexViewScala)(FrontendStylesLinkBlackPlaceholder, 182 | s"""DemoStyles.underlineLinkBlack, """.stripMargin) 183 | 184 | appendOnPlaceholder(indexViewScala)(FrontendImportsPlaceholder, 185 | s""" 186 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.{DemoStyles, GlobalStyles} 187 | |import scalacss.ScalatagsCss._""".stripMargin) 188 | 189 | if (rpcDemoViewScala.exists()) appendOnPlaceholder(rpcDemoViewScala)(FrontendImportsPlaceholder, 190 | s""" 191 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.DemoStyles 192 | |import scalacss.ScalatagsCss._""".stripMargin) 193 | 194 | if (rpcDemoViewScala.exists()) appendOnPlaceholder(rpcDemoViewScala)(FrontendStylesLinkBlackPlaceholder, 195 | s"""(DemoStyles.underlineLinkBlack)""".stripMargin) 196 | 197 | writeFile(demoStylesViewScala)( 198 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()} 199 | | 200 | |import io.udash._ 201 | |import ${settings.rootPackage.mkPackage()}.$stateName 202 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.DemoStyles 203 | |import org.scalajs.dom.Element 204 | | 205 | |import scala.language.postfixOps 206 | | 207 | |case object DemoStylesViewPresenter extends DefaultViewPresenterFactory[$stateName.type](() => new DemoStylesView) 208 | | 209 | |class DemoStylesView extends View { 210 | | import scalacss.DevDefaults._ 211 | | import scalacss.ScalatagsCss._ 212 | | import scalatags.JsDom._ 213 | | import scalatags.JsDom.all._ 214 | | 215 | | private val content = div( 216 | | LocalStyles.render[TypedTag[org.scalajs.dom.raw.HTMLStyleElement]], 217 | | h2( 218 | | "You can find this demo source code in: ", 219 | | i("${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.DemoStylesView") 220 | | ), 221 | | h3("Example"), 222 | | p(LocalStyles.redItalic)("Red italic text."), 223 | | p(LocalStyles.obliqueOnHover)("Hover me!"), 224 | | h3("Read more"), 225 | | ul( 226 | | li( 227 | | a(DemoStyles.underlineLinkBlack)(href := "${settings.udashDevGuide}#/frontend/templates", target := "_blank")("Read more in Udash Guide.") 228 | | ), 229 | | li( 230 | | a(DemoStyles.underlineLinkBlack)(href := "https://japgolly.github.io/scalacss/book/", target := "_blank")("Read more in ScalaCSS docs.") 231 | | ) 232 | | ) 233 | | ) 234 | | 235 | | override def getTemplate: Modifier = content 236 | | 237 | | override def renderChild(view: View): Unit = {} 238 | | 239 | | object LocalStyles extends StyleSheet.Inline { 240 | | import dsl._ 241 | | 242 | | val redItalic = style( 243 | | fontStyle.italic, 244 | | color.red 245 | | ) 246 | | 247 | | val obliqueOnHover = style( 248 | | fontStyle.normal, 249 | | 250 | | &.hover( 251 | | fontStyle.oblique 252 | | ) 253 | | ) 254 | | } 255 | |} 256 | |""".stripMargin 257 | ) 258 | 259 | appendOnPlaceholder(initScala)(FrontendAppInitPlaceholder, 260 | s""" 261 | | 262 | |import scalacss.DevDefaults._ 263 | |import scalacss.ScalatagsCss._ 264 | |import scalatags.JsDom._ 265 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.GlobalStyles 266 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.DemoStyles 267 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.partials.FooterStyles 268 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.partials.HeaderStyles 269 | |jQ(GlobalStyles.render[TypedTag[org.scalajs.dom.raw.HTMLStyleElement]].render).insertBefore(appRoot.get) 270 | |jQ(DemoStyles.render[TypedTag[org.scalajs.dom.raw.HTMLStyleElement]].render).insertBefore(appRoot.get) 271 | |jQ(FooterStyles.render[TypedTag[org.scalajs.dom.raw.HTMLStyleElement]].render).insertBefore(appRoot.get) 272 | |jQ(HeaderStyles.render[TypedTag[org.scalajs.dom.raw.HTMLStyleElement]].render).insertBefore(appRoot.get)""".stripMargin) 273 | 274 | writeFile(globalStylesScala)( 275 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()} 276 | | 277 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName}.StyleConstants 278 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesFontsPackage.getName}.{FontStyle, FontWeight, UdashFonts} 279 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName}.{MediaQueries, StyleUtils} 280 | | 281 | |import scala.language.postfixOps 282 | |import scalacss.internal.{Attr, Literal} 283 | |import scalacss.DevDefaults._ 284 | | 285 | |object GlobalStyles extends StyleSheet.Inline { 286 | | import dsl._ 287 | | 288 | | val reset = style( 289 | | unsafeRoot(\"\"\"html, body, div, span, applet, object, iframe, 290 | | | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 291 | | | a, abbr, acronym, address, big, cite, code, 292 | | | del, dfn, em, img, ins, kbd, q, s, samp, 293 | | | small, strike, strong, sub, sup, tt, var, 294 | | | b, u, i, center, 295 | | | dl, dt, dd, ol, ul, li, 296 | | | fieldset, form, label, legend, 297 | | | table, caption, tbody, tfoot, thead, tr, th, td, 298 | | | article, aside, canvas, details, embed, 299 | | | figure, figcaption, footer, header, hgroup, 300 | | | menu, nav, output, ruby, section, summary, 301 | | | time, mark, audio, video\"\"\".stripMargin.replaceAll("\\\\s+", ""))( 302 | | margin.`0`, 303 | | padding.`0`, 304 | | border.`0`, 305 | | fontSize(100 %%), 306 | | font := Literal.inherit, 307 | | verticalAlign.baseline 308 | | ), 309 | | 310 | | unsafeRoot("article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section")( 311 | | display.block 312 | | ), 313 | | 314 | | unsafeRoot("body")( 315 | | lineHeight(1) 316 | | ), 317 | | 318 | | unsafeRoot("ol,ul")( 319 | | listStyle := none // TODO 320 | | ), 321 | | 322 | | unsafeRoot ("blockquote, q")( 323 | | quotes.none 324 | | ), 325 | | 326 | | unsafeRoot("blockquote:before, blockquote:after, q:before, q:after")( 327 | | content := "''", 328 | | content := none 329 | | ), 330 | | 331 | | unsafeRoot("table")( 332 | | borderCollapse.collapse, 333 | | borderSpacing.`0` 334 | | ) 335 | | ) 336 | | 337 | | val global = style( 338 | | unsafeRoot("#application") ( 339 | | height(100 %%) 340 | | ), 341 | | 342 | | unsafeRoot("html") ( 343 | | UdashFonts.acumin(), 344 | | position.relative, 345 | | height(100 %%), 346 | | fontSize(62.5 %%), 347 | | overflowY.scroll 348 | | ), 349 | | 350 | | unsafeRoot("body") ( 351 | | position.relative, 352 | | height(100 %%), 353 | | fontSize(1.6 rem) 354 | | ), 355 | | 356 | | unsafeRoot("p")( 357 | | marginTop(1 rem), 358 | | fontSize(1.6 rem), 359 | | lineHeight(1.3), 360 | | 361 | | &.firstChild ( 362 | | marginTop(`0`) 363 | | ) 364 | | ), 365 | | 366 | | unsafeRoot("li")( 367 | | fontSize.inherit, 368 | | lineHeight(1.3) 369 | | ), 370 | | 371 | | unsafeRoot("h1") ( 372 | | position.relative, 373 | | UdashFonts.acumin(FontWeight.SemiBold), 374 | | paddingTop(7 rem), 375 | | paddingBottom(4.5 rem), 376 | | lineHeight(1.2), 377 | | fontSize(4.8 rem), 378 | | 379 | | &.after ( 380 | | content := "\\"\\u2014\\"", 381 | | position.absolute, 382 | | left(`0`), 383 | | bottom(`0`), 384 | | fontSize(3.6 rem) 385 | | ), 386 | | 387 | | MediaQueries.phone( 388 | | style( 389 | | fontSize(3.2 rem) 390 | | ) 391 | | ) 392 | | ), 393 | | 394 | | unsafeRoot("h2") ( 395 | | UdashFonts.acumin(FontWeight.Regular), 396 | | marginBottom(2 rem), 397 | | lineHeight(1.2), 398 | | fontSize(2.5 rem), 399 | | wordWrap.breakWord, 400 | | 401 | | MediaQueries.phone( 402 | | style( 403 | | fontSize(2.8 rem) 404 | | ) 405 | | ) 406 | | ), 407 | | 408 | | unsafeRoot("h3") ( 409 | | UdashFonts.acumin(FontWeight.ExtraLight), 410 | | marginTop(4.5 rem), 411 | | marginBottom(1.5 rem), 412 | | lineHeight(1.2), 413 | | fontSize(2.2 rem), 414 | | 415 | | MediaQueries.phone( 416 | | style( 417 | | fontSize(2.6 rem) 418 | | ) 419 | | ) 420 | | ), 421 | | 422 | | unsafeRoot("h4") ( 423 | | UdashFonts.acumin(FontWeight.ExtraLight), 424 | | marginTop(3.5 rem), 425 | | marginBottom(1.5 rem), 426 | | lineHeight(1.2), 427 | | fontSize(2.4 rem), 428 | | 429 | | MediaQueries.phone( 430 | | style( 431 | | fontSize(2 rem) 432 | | ) 433 | | ) 434 | | ), 435 | | 436 | | unsafeRoot("blockquote") ( 437 | | UdashFonts.acumin(FontWeight.ExtraLight, FontStyle.Italic), 438 | | position.relative, 439 | | margin(4 rem, `0`, 5 rem, 4.5 rem), 440 | | padding(1.5 rem, 3 rem), 441 | | fontSize(3.2 rem), 442 | | color(StyleConstants.Colors.Grey), 443 | | 444 | | &.before( 445 | | StyleUtils.border(StyleConstants.Colors.Red, .3 rem), 446 | | content := "\\" \\"", 447 | | position.absolute, 448 | | top(`0`), 449 | | left(`0`), 450 | | height(100 %%) 451 | | ), 452 | | 453 | | MediaQueries.phone( 454 | | style( 455 | | fontSize(2.4 rem) 456 | | ) 457 | | ) 458 | | ), 459 | | 460 | | unsafeRoot("a") ( 461 | | textDecoration := "none", 462 | | outline(`0`).important, 463 | | 464 | | &.link( 465 | | textDecoration := "none" 466 | | ), 467 | | 468 | | &.hover ( 469 | | textDecoration := "none" 470 | | ), 471 | | 472 | | &.visited ( 473 | | color.inherit 474 | | ), 475 | | 476 | | &.hover ( 477 | | textDecoration := "underline" 478 | | ) 479 | | ), 480 | | 481 | | unsafeRoot("img")( 482 | | maxWidth(100 %%), 483 | | height.auto 484 | | ), 485 | | 486 | | unsafeRoot("svg") ( 487 | | display.block 488 | | ), 489 | | 490 | | unsafeRoot("object[type='image/svg+xml']") ( 491 | | display.block, 492 | | pointerEvents := "none" 493 | | ), 494 | | 495 | | unsafeRoot("input") ( 496 | | padding(.5 rem, 1 rem), 497 | | borderWidth(1 px), 498 | | borderStyle.solid, 499 | | borderColor(c"#cccccc"), 500 | | borderRadius(.3 rem), 501 | | 502 | | &.focus ( 503 | | outline.none 504 | | ) 505 | | ), 506 | | 507 | | unsafeRoot("input::-webkit-outer-spin-button")( 508 | | Attr.real("-webkit-appearance") := "none", 509 | | margin(`0`) 510 | | ), 511 | | 512 | | unsafeRoot("input::-webkit-inner-spin-button")( 513 | | Attr.real("-webkit-appearance") := "none", 514 | | margin(`0`) 515 | | ), 516 | | 517 | | unsafeRoot("textarea") ( 518 | | resize.none 519 | | ), 520 | | 521 | | unsafeRoot("strong")( 522 | | fontWeight.bolder 523 | | ), 524 | | 525 | | unsafeRoot("b")( 526 | | fontWeight.bold 527 | | ), 528 | | 529 | | unsafeRoot("i")( 530 | | fontStyle.italic, 531 | | fontWeight._600 532 | | ), 533 | | 534 | | unsafeRoot("*") ( 535 | | boxSizing.borderBox, 536 | | 537 | | &.before ( 538 | | boxSizing.borderBox 539 | | ), 540 | | 541 | | &.after ( 542 | | boxSizing.borderBox 543 | | ) 544 | | ) 545 | | ) 546 | | 547 | | val clearfix = style( 548 | | &.before ( 549 | | content := "\\" \\"", 550 | | display.table 551 | | ), 552 | | 553 | | &.after ( 554 | | content := "\\" \\"", 555 | | display.table, 556 | | clear.both 557 | | ) 558 | | ) 559 | | 560 | | val col = style( 561 | | position.relative, 562 | | display.inlineBlock, 563 | | verticalAlign.top 564 | | ) 565 | | 566 | | val body = style( 567 | | position.relative, 568 | | width(StyleConstants.Sizes.BodyWidth px), 569 | | height(100 %%), 570 | | margin(0 px, auto), 571 | | 572 | | MediaQueries.tabletLandscape( 573 | | style( 574 | | width(100 %%), 575 | | paddingLeft(2 rem), 576 | | paddingRight(2 rem) 577 | | ) 578 | | ), 579 | | 580 | | MediaQueries.phone( 581 | | style( 582 | | paddingLeft(3 %%), 583 | | paddingRight(3 %%) 584 | | ) 585 | | ) 586 | | ) 587 | | 588 | | val main = style( 589 | | position.relative, 590 | | minHeight :=! s"calc(100vh - $${StyleConstants.Sizes.HeaderHeight}px - $${StyleConstants.Sizes.FooterHeight}px)", 591 | | paddingBottom(10 rem) 592 | | ) 593 | | 594 | | val block = style( 595 | | display.block 596 | | ) 597 | | 598 | | val inline = style( 599 | | display.inline 600 | | ) 601 | | 602 | | val hidden = style( 603 | | visibility.hidden 604 | | ) 605 | | 606 | | val noMargin = style( 607 | | margin(`0`).important 608 | | ) 609 | | 610 | | val red = style( 611 | | color(StyleConstants.Colors.Red).important 612 | | ) 613 | | 614 | | val grey = style( 615 | | color(StyleConstants.Colors.Grey).important 616 | | ) 617 | |} 618 | |""".stripMargin) 619 | 620 | writeFile(demoStylesScala)( 621 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()} 622 | | 623 | |import java.util.concurrent.TimeUnit 624 | | 625 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName}.{MediaQueries, StyleUtils} 626 | | 627 | |import scala.concurrent.duration.FiniteDuration 628 | |import scala.language.postfixOps 629 | |import scalacss.internal.Compose 630 | |import scalacss.DevDefaults._ 631 | | 632 | |object DemoStyles extends StyleSheet.Inline { 633 | | import dsl._ 634 | | 635 | | val linkHoverAnimation = keyframes( 636 | | (0 %%) -> keyframe(color.black), 637 | | (50 %%) -> keyframe(color.red), 638 | | (100 %%) -> keyframe(color.black) 639 | | ) 640 | | 641 | | val navItem = style( 642 | | position.relative, 643 | | display.inlineBlock, 644 | | verticalAlign.middle, 645 | | paddingLeft(1.8 rem), 646 | | paddingRight(1.8 rem), 647 | | 648 | | &.firstChild ( 649 | | paddingLeft(0 px) 650 | | ), 651 | | 652 | | &.lastChild ( 653 | | paddingRight(0 px) 654 | | ), 655 | | 656 | | &.before.not(_.firstChild)( 657 | | StyleUtils.absoluteMiddle, 658 | | content := "\\"|\\"", 659 | | left(`0`), 660 | | 661 | | &.hover( 662 | | textDecoration := "none" 663 | | ) 664 | | ) 665 | | ) 666 | | 667 | | val underlineLink = style( 668 | | position.relative, 669 | | display.block, 670 | | color.white, 671 | | 672 | | &.after( 673 | | StyleUtils.transition(transform, new FiniteDuration(250, TimeUnit.MILLISECONDS)), 674 | | position.absolute, 675 | | top(100 %%), 676 | | left(`0`), 677 | | content := "\\" \\"", 678 | | width(100 %%), 679 | | borderBottomColor.white, 680 | | borderBottomWidth(1 px), 681 | | borderBottomStyle.solid, 682 | | transform := "scaleX(0)", 683 | | transformOrigin := "100% 50%" 684 | | ), 685 | | 686 | | &.hover( 687 | | color.white, 688 | | cursor.pointer, 689 | | textDecoration := "none", 690 | | 691 | | &.after ( 692 | | transformOrigin := "0 50%", 693 | | transform := "scaleX(1)" 694 | | ) 695 | | ) 696 | | ) 697 | | 698 | | val underlineLinkBlack = style( 699 | | underlineLink, 700 | | display.inlineBlock, 701 | | color.black, 702 | | 703 | | &.after( 704 | | borderBottomColor.black 705 | | ), 706 | | 707 | | &.hover ( 708 | | color.black 709 | | ) 710 | | )(Compose.trust) 711 | | 712 | | private val liBulletStyle = style( 713 | | position.absolute, 714 | | left(`0`), 715 | | top(`0`) 716 | | ) 717 | | 718 | | private val liStyle = style( 719 | | position.relative, 720 | | paddingLeft(2 rem), 721 | | margin(.5 rem, `0`, .5 rem, 4.5 rem), 722 | | 723 | | MediaQueries.phone( 724 | | style( 725 | | marginLeft(1.5 rem) 726 | | ) 727 | | ) 728 | | ) 729 | | 730 | | val stepsList = style( 731 | | counterReset := "steps", 732 | | unsafeChild("li") ( 733 | | liStyle, 734 | | 735 | | &.before( 736 | | liBulletStyle, 737 | | counterIncrement := "steps", 738 | | content := "counters(steps, '.')\\".\\"" 739 | | ) 740 | | ) 741 | | ) 742 | |} 743 | |""".stripMargin) 744 | 745 | writeFile(configScala)( 746 | s"""package ${settings.rootPackage.mkPackage()}.${configPackage.getName} 747 | |object ExternalUrls { 748 | | val udashGithub = "https://github.com/UdashFramework/" 749 | | 750 | | val udashDemos = "https://github.com/UdashFramework/udash-demos" 751 | | 752 | | val stackoverflow = "http://stackoverflow.com/questions/tagged/udash" 753 | | 754 | | val avsystem = "http://www.avsystem.com/" 755 | | 756 | | val homepage = "http://udash.io/" 757 | |} 758 | |""".stripMargin) 759 | 760 | writeFile(stylesConstantsScala)( 761 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName} 762 | |import scalacss.DevDefaults._ 763 | | 764 | |object StyleConstants extends StyleSheet.Inline{ 765 | | import dsl._ 766 | | 767 | | /** 768 | | * SIZES 769 | | */ 770 | | object Sizes { 771 | | val BodyWidth = 1075 772 | | 773 | | val MinSiteHeight = 550 774 | | 775 | | val HeaderHeight = 80 776 | | 777 | | val HeaderHeightMobile = 80 778 | | 779 | | val FooterHeight = 120 780 | | 781 | | val MobileMenuButton = 50 782 | | } 783 | | 784 | | /** 785 | | * COLORS 786 | | */ 787 | | object Colors { 788 | | val Red = c"#e30613" 789 | | 790 | | val RedLight = c"#ff2727" 791 | | 792 | | val GreyExtra = c"#ebebeb" 793 | | 794 | | val GreySemi = c"#cfcfd6" 795 | | 796 | | val Grey = c"#777785" 797 | | 798 | | val Yellow = c"#ffd600" 799 | | 800 | | val Cyan = c"#eef4f7" 801 | | } 802 | | 803 | | /** 804 | | * MEDIA QUERIES 805 | | */ 806 | | object MediaQueriesBounds { 807 | | val TabletLandscapeMax = Sizes.BodyWidth - 1 808 | | 809 | | val TabletLandscapeMin = 768 810 | | 811 | | val TabletMax = TabletLandscapeMin - 1 812 | | 813 | | val TabletMin = 481 814 | | 815 | | val PhoneMax = TabletMin - 1 816 | | } 817 | |} 818 | |""".stripMargin) 819 | 820 | writeFile(stylesFontsScala)( 821 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesFontsPackage.getName} 822 | |import scala.language.postfixOps 823 | |import scalacss.internal.AV 824 | |import scalacss.DevDefaults._ 825 | | 826 | |object UdashFonts extends StyleSheet.Inline { 827 | | import dsl._ 828 | | 829 | | def acumin(fontWeight: AV = FontWeight.Regular, fontStyle: AV = FontStyle.Normal) = style( 830 | | fontFamily :=! FontFamily.Acumin, 831 | | fontStyle, 832 | | fontWeight 833 | | ) 834 | |} 835 | | 836 | |object FontFamily { 837 | | val Acumin = "'acumin-pro', san-serif" 838 | |} 839 | | 840 | |object FontWeight extends StyleSheet.Inline { 841 | | import dsl._ 842 | | val ExtraLight: AV = fontWeight._200 843 | | val Light: AV = fontWeight._300 844 | | val Regular: AV = fontWeight._400 845 | | val SemiBold: AV = fontWeight._600 846 | | val Bold: AV = fontWeight._700 847 | |} 848 | | 849 | |object FontStyle extends StyleSheet.Inline { 850 | | import dsl._ 851 | | val Normal: AV = fontStyle.normal 852 | | val Italic: AV = fontStyle.italic 853 | |} 854 | |""".stripMargin) 855 | 856 | writeFile(stylesHeaderScala)( 857 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesPartialsPackage.getName} 858 | |import java.util.concurrent.TimeUnit 859 | | 860 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName}.StyleConstants 861 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName}.{MediaQueries, StyleUtils} 862 | | 863 | |import scala.concurrent.duration.FiniteDuration 864 | |import scala.language.postfixOps 865 | |import scalacss.DevDefaults._ 866 | | 867 | |object HeaderStyles extends StyleSheet.Inline { 868 | | import dsl._ 869 | | 870 | | val header = style( 871 | | position.relative, 872 | | backgroundColor.black, 873 | | height(StyleConstants.Sizes.HeaderHeight px), 874 | | fontSize(1.6 rem), 875 | | zIndex(99), 876 | | 877 | | MediaQueries.tabletPortrait( 878 | | style( 879 | | height(StyleConstants.Sizes.HeaderHeight * .7 px) 880 | | ) 881 | | ) 882 | | ) 883 | | 884 | | val headerLeft = style( 885 | | position.relative, 886 | | float.left, 887 | | height(100 %%) 888 | | ) 889 | | 890 | | val headerRight = style( 891 | | position.relative, 892 | | float.right, 893 | | height(100 %%) 894 | | ) 895 | | 896 | | val headerLogo = style( 897 | | StyleUtils.relativeMiddle, 898 | | display.inlineBlock, 899 | | verticalAlign.top, 900 | | width(130 px), 901 | | marginRight(25 px), 902 | | 903 | | MediaQueries.tabletPortrait( 904 | | style( 905 | | width(130 * .8 px) 906 | | ) 907 | | ) 908 | | ) 909 | | 910 | | val headerNav = style( 911 | | StyleUtils.relativeMiddle, 912 | | display.inlineBlock, 913 | | verticalAlign.top, 914 | | color.white 915 | | ) 916 | | 917 | | val headerSocial = style( 918 | | StyleUtils.relativeMiddle 919 | | ) 920 | | 921 | | val headerSocialItem = style( 922 | | display.inlineBlock, 923 | | marginLeft(2 rem) 924 | | ) 925 | | 926 | | private val socialLink = style( 927 | | position.relative, 928 | | display.block, 929 | | width(33 px), 930 | | 931 | | unsafeChild("svg") ( 932 | | StyleUtils.transition() 933 | | ), 934 | | 935 | | MediaQueries.tabletPortrait( 936 | | style( 937 | | width(25 px) 938 | | ) 939 | | ) 940 | | ) 941 | | 942 | | val headerSocialLink = style( 943 | | socialLink, 944 | | 945 | | unsafeChild("svg") ( 946 | | svgFill := c"#fff" 947 | | ), 948 | | 949 | | &.hover ( 950 | | unsafeChild("svg") ( 951 | | svgFill := StyleConstants.Colors.Red 952 | | ) 953 | | ) 954 | | ) 955 | | 956 | | val headerSocialLinkYellow = style( 957 | | socialLink, 958 | | 959 | | unsafeChild("svg") ( 960 | | svgFill := StyleConstants.Colors.Yellow 961 | | ), 962 | | 963 | | &.hover ( 964 | | unsafeChild(s".$${tooltip.htmlClass}")( 965 | | visibility.visible, 966 | | opacity(1) 967 | | ), 968 | | 969 | | unsafeChild(s".$${tooltipTop.htmlClass}")( 970 | | transitionDelay(new FiniteDuration(0, TimeUnit.MILLISECONDS)), 971 | | transform := "scaleX(1)" 972 | | ), 973 | | 974 | | unsafeChild(s".$${tooltipTextInner.htmlClass}")( 975 | | transitionDelay(new FiniteDuration(350, TimeUnit.MILLISECONDS)), 976 | | transform := "translateY(0)" 977 | | ) 978 | | ) 979 | | ) 980 | | 981 | | lazy val tooltip = style( 982 | | StyleUtils.transition(new FiniteDuration(150, TimeUnit.MILLISECONDS)), 983 | | position.absolute, 984 | | top :=! "calc(100% + 10px)", 985 | | right(`0`), 986 | | fontSize(1.2 rem), 987 | | color.black, 988 | | textAlign.center, 989 | | visibility.hidden, 990 | | opacity(0), 991 | | pointerEvents := "none", 992 | | 993 | | MediaQueries.tabletLandscape( 994 | | style( 995 | | display.none 996 | | ) 997 | | ) 998 | | ) 999 | | 1000 | | lazy val tooltipTop = style( 1001 | | StyleUtils.transition(new FiniteDuration(350, TimeUnit.MILLISECONDS)), 1002 | | transitionDelay(new FiniteDuration(200, TimeUnit.MILLISECONDS)), 1003 | | position.relative, 1004 | | width(100 %%), 1005 | | backgroundColor(StyleConstants.Colors.Red), 1006 | | height(4 px), 1007 | | transformOrigin := "calc(100% - 9px) 0", 1008 | | transform := "scaleX(.2)", 1009 | | zIndex(9), 1010 | | 1011 | | &.after( 1012 | | content := "\\" \\"", 1013 | | position.absolute, 1014 | | bottom :=! "calc(100% - 1px)", 1015 | | right(9 px), 1016 | | marginLeft(-6 px), 1017 | | width(`0`), 1018 | | height(`0`), 1019 | | borderBottomWidth(6 px), 1020 | | borderBottomStyle.solid, 1021 | | borderBottomColor(StyleConstants.Colors.Red), 1022 | | borderRightWidth(6 px), 1023 | | borderRightStyle.solid, 1024 | | borderRightColor.transparent, 1025 | | borderLeftWidth(6 px), 1026 | | borderLeftStyle.solid, 1027 | | borderLeftColor.transparent 1028 | | ) 1029 | | ) 1030 | | 1031 | | val tooltipText = style( 1032 | | position.relative, 1033 | | width(100 %%), 1034 | | overflow.hidden 1035 | | ) 1036 | | 1037 | | lazy val tooltipTextInner = style( 1038 | | StyleUtils.transition(new FiniteDuration(200, TimeUnit.MILLISECONDS)), 1039 | | position.relative, 1040 | | width(100 %%), 1041 | | padding(10 px, 15 px), 1042 | | color.white, 1043 | | backgroundColor(StyleConstants.Colors.RedLight), 1044 | | whiteSpace.nowrap, 1045 | | transform := "translateY(-100%)" 1046 | | ) 1047 | |} 1048 | |""".stripMargin) 1049 | 1050 | 1051 | writeFile(stylesFooterScala)( 1052 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesPartialsPackage.getName} 1053 | | 1054 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName}.StyleConstants 1055 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesFontsPackage.getName}.{FontWeight, UdashFonts} 1056 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName}.{MediaQueries, StyleUtils} 1057 | | 1058 | |import scala.language.postfixOps 1059 | |import scalacss.DevDefaults._ 1060 | | 1061 | |object FooterStyles extends StyleSheet.Inline { 1062 | | import dsl._ 1063 | | 1064 | | val footer = style( 1065 | | backgroundColor.black, 1066 | | height(StyleConstants.Sizes.FooterHeight px), 1067 | | fontSize(1.2 rem), 1068 | | color.white, 1069 | | 1070 | | MediaQueries.phone( 1071 | | style( 1072 | | height.auto, 1073 | | padding(2 rem, `0`) 1074 | | ) 1075 | | ) 1076 | | ) 1077 | | 1078 | | val footerInner = style( 1079 | | StyleUtils.relativeMiddle, 1080 | | 1081 | | MediaQueries.phone( 1082 | | style( 1083 | | top.auto, 1084 | | transform := "none" 1085 | | ) 1086 | | ) 1087 | | ) 1088 | | 1089 | | val footerLogo = style( 1090 | | display.inlineBlock, 1091 | | verticalAlign.middle, 1092 | | width(50 px), 1093 | | marginRight(25 px) 1094 | | ) 1095 | | 1096 | | val footerLinks = style( 1097 | | display.inlineBlock, 1098 | | verticalAlign.middle 1099 | | ) 1100 | | 1101 | | val footerMore = style( 1102 | | UdashFonts.acumin(FontWeight.SemiBold), 1103 | | marginBottom(1.5 rem), 1104 | | fontSize(2.2 rem) 1105 | | ) 1106 | | 1107 | | val footerCopyrights = style( 1108 | | position.absolute, 1109 | | right(`0`), 1110 | | bottom(`0`), 1111 | | fontSize.inherit, 1112 | | 1113 | | MediaQueries.tabletPortrait( 1114 | | style( 1115 | | position.relative, 1116 | | textAlign.right 1117 | | ) 1118 | | ) 1119 | | ) 1120 | | 1121 | | val footerAvsystemLink = style( 1122 | | StyleUtils.transition(), 1123 | | color.inherit, 1124 | | textDecoration := "underline", 1125 | | 1126 | | &.hover ( 1127 | | color(StyleConstants.Colors.Yellow) 1128 | | ), 1129 | | 1130 | | &.visited ( 1131 | | color.inherit, 1132 | | 1133 | | &.hover ( 1134 | | color(StyleConstants.Colors.Yellow) 1135 | | ) 1136 | | ) 1137 | | ) 1138 | |} 1139 | |""".stripMargin) 1140 | 1141 | writeFile(stylesMediaQueriesScala)( 1142 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName} 1143 | | 1144 | | 1145 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName}.StyleConstants 1146 | | 1147 | |import scalacss.DevDefaults._ 1148 | |import scala.language.postfixOps 1149 | | 1150 | |object MediaQueries extends StyleSheet.Inline { 1151 | | import dsl._ 1152 | | 1153 | | def tabletLandscape(properties: StyleA) = style( 1154 | | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletLandscapeMax px) ( 1155 | | properties 1156 | | ) 1157 | | ) 1158 | | 1159 | | def tabletPortrait(properties: StyleA) = style( 1160 | | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.TabletMax px) ( 1161 | | properties 1162 | | ) 1163 | | ) 1164 | | 1165 | | def phone(properties: StyleA) = style( 1166 | | media.screen.minWidth(1 px).maxWidth(StyleConstants.MediaQueriesBounds.PhoneMax px) ( 1167 | | properties 1168 | | ) 1169 | | ) 1170 | |} 1171 | |""".stripMargin) 1172 | 1173 | writeFile(stylesStyleUtilsScala)( 1174 | s"""package ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesUtilsPackage.getName} 1175 | |import java.util.concurrent.TimeUnit 1176 | | 1177 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesConstantsPackage.getName}.StyleConstants 1178 | | 1179 | |import scala.concurrent.duration.FiniteDuration 1180 | |import scala.language.postfixOps 1181 | |import scalacss.internal.{AV, Attr, Length, ValueT} 1182 | |import scalacss.DevDefaults._ 1183 | | 1184 | |object StyleUtils extends StyleSheet.Inline { 1185 | | import dsl._ 1186 | | 1187 | | val middle = style( 1188 | | top(50 %%), 1189 | | transform := "translateY(-50%)" 1190 | | ) 1191 | | 1192 | | val center = style( 1193 | | top(50 %%), 1194 | | left(50 %%), 1195 | | transform := "translateY(-50%) translateX(-50%)" 1196 | | ) 1197 | | 1198 | | val relativeMiddle = style( 1199 | | middle, 1200 | | position.relative 1201 | | ) 1202 | | 1203 | | val absoluteMiddle = style( 1204 | | middle, 1205 | | position.absolute 1206 | | ) 1207 | | 1208 | | val absoluteCenter = style( 1209 | | center, 1210 | | position.absolute 1211 | | ) 1212 | | 1213 | | def transition(): StyleA = style( 1214 | | transitionProperty := "all", 1215 | | transitionDuration(new FiniteDuration(250, TimeUnit.MILLISECONDS)), 1216 | | transitionTimingFunction.easeInOut 1217 | | ) 1218 | | 1219 | | def transition(duration: FiniteDuration): StyleA = style( 1220 | | transitionProperty := "all", 1221 | | transitionDuration(duration), 1222 | | transitionTimingFunction.easeInOut 1223 | | ) 1224 | | 1225 | | def transition(property: Attr, duration: FiniteDuration): StyleA = style( 1226 | | transitionProperty := property.toString(), 1227 | | transitionDuration(duration), 1228 | | transitionTimingFunction.easeInOut 1229 | | ) 1230 | | 1231 | | def border(bColor: ValueT[ValueT.Color] = StyleConstants.Colors.GreyExtra, bWidth: Length[Double] = 1.0 px, bStyle: AV = borderStyle.solid): StyleA = style( 1232 | | borderWidth(bWidth), 1233 | | bStyle, 1234 | | borderColor(bColor) 1235 | | ) 1236 | | 1237 | | def bShadow(x: Int = 2, y: Int = 2, blur: Int = 5, spread: Int = 0, color: ValueT[ValueT.Color] = c"#000000", opacity: Double = .4, inset: Boolean = false): StyleA = style( 1238 | | boxShadow := s"$${if (inset) "inset " else ""}$${x}px $${y}px $${blur}px $${spread}px $${hexToRGBA(color, opacity)}" 1239 | | ) 1240 | | 1241 | | private def hexToRGBA(color: ValueT[ValueT.Color], opacity: Double = 1): String = { 1242 | | val cNumber = Integer.parseInt(color.value.replace("#", ""), 16) 1243 | | val r = (cNumber.toInt >> 16) & 0xFF 1244 | | val g = (cNumber.toInt >> 8) & 0xFF 1245 | | val b = (cNumber.toInt >> 0) & 0xFF 1246 | | 1247 | | s"rgba($$r, $$g, $$b, $$opacity)" 1248 | | } 1249 | |} 1250 | |""".stripMargin) 1251 | 1252 | writeFile(footerScala)( 1253 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.${componentsPackage.getName} 1254 | |import ${settings.rootPackage.mkPackage()}.${configPackage.getName}.ExternalUrls 1255 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.{DemoStyles, GlobalStyles} 1256 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesPartialsPackage.getName}.FooterStyles 1257 | |import org.scalajs.dom.raw.Element 1258 | | 1259 | |import scalatags.JsDom.all._ 1260 | |import scalacss.ScalatagsCss._ 1261 | | 1262 | |object Footer { 1263 | | private lazy val template = footer(FooterStyles.footer)( 1264 | | div(GlobalStyles.body)( 1265 | | div(FooterStyles.footerInner)( 1266 | | a(FooterStyles.footerLogo, href := ExternalUrls.homepage)( 1267 | | Image("udash_logo.png", "Udash Framework", GlobalStyles.block) 1268 | | ), 1269 | | div(FooterStyles.footerLinks)( 1270 | | p(FooterStyles.footerMore)("See more"), 1271 | | ul( 1272 | | li(DemoStyles.navItem)( 1273 | | a(href := ExternalUrls.udashDemos, target := "_blank", DemoStyles.underlineLink)("Github demo") 1274 | | ), 1275 | | li(DemoStyles.navItem)( 1276 | | a(href := ExternalUrls.stackoverflow, target := "_blank", DemoStyles.underlineLink)("StackOverflow questions") 1277 | | ) 1278 | | ) 1279 | | ), 1280 | | p(FooterStyles.footerCopyrights)("Proudly made by ", a(FooterStyles.footerAvsystemLink, href := ExternalUrls.avsystem, target := "_blank")("AVSystem")) 1281 | | ) 1282 | | ) 1283 | | ).render 1284 | | 1285 | | def getTemplate: Element = template 1286 | |} 1287 | |""".stripMargin) 1288 | 1289 | writeFile(headerScala)( 1290 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.${componentsPackage.getName} 1291 | |import ${settings.rootPackage.mkPackage()}.IndexState 1292 | |import ${settings.rootPackage.mkPackage()}.${configPackage.getName}.ExternalUrls 1293 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.GlobalStyles 1294 | |import ${settings.rootPackage.mkPackage()}.${settings.stylesSubPackage.mkPackage()}.${stylesPartialsPackage.getName}.HeaderStyles 1295 | |import org.scalajs.dom.raw.Element 1296 | | 1297 | |import scalatags.JsDom.all._ 1298 | |import scalacss.ScalatagsCss._ 1299 | |import ${settings.rootPackage.mkPackage()}.Context._ 1300 | | 1301 | |object Header { 1302 | | private lazy val template = header(HeaderStyles.header)( 1303 | | div(GlobalStyles.body, GlobalStyles.clearfix)( 1304 | | div(HeaderStyles.headerLeft)( 1305 | | a(HeaderStyles.headerLogo, href := IndexState.url)( 1306 | | Image("udash_logo_m.png", "Udash Framework", GlobalStyles.block) 1307 | | ) 1308 | | ), 1309 | | div(HeaderStyles.headerRight)( 1310 | | ul(HeaderStyles.headerSocial)( 1311 | | li(HeaderStyles.headerSocialItem)( 1312 | | a(href := ExternalUrls.udashGithub, HeaderStyles.headerSocialLink, target := "_blank")( 1313 | | Image("icon_github.png", "Github") 1314 | | ) 1315 | | ), 1316 | | li(HeaderStyles.headerSocialItem)( 1317 | | a(href := ExternalUrls.stackoverflow, HeaderStyles.headerSocialLink, target := "_blank")( 1318 | | Image("icon_stackoverflow.png", "StackOverflow") 1319 | | ) 1320 | | ), 1321 | | li(HeaderStyles.headerSocialItem)( 1322 | | a(href := ExternalUrls.avsystem, HeaderStyles.headerSocialLinkYellow, target := "_blank")( 1323 | | Image("icon_avsystem.png", "Proudly made by AVSystem"), 1324 | | div(HeaderStyles.tooltip)( 1325 | | div(HeaderStyles.tooltipTop), 1326 | | div(HeaderStyles.tooltipText)( 1327 | | div(HeaderStyles.tooltipTextInner)( 1328 | | "Proudly made by AVSystem" 1329 | | ) 1330 | | ) 1331 | | ) 1332 | | ) 1333 | | ) 1334 | | ) 1335 | | ) 1336 | | ) 1337 | | ).render 1338 | | 1339 | | def getTemplate: Element = template 1340 | |} 1341 | |""".stripMargin) 1342 | 1343 | writeFile(imageFactoryScala)( 1344 | s"""package ${settings.rootPackage.mkPackage()}.${settings.viewsSubPackage.mkPackage()}.${componentsPackage.getName} 1345 | |import org.scalajs.dom 1346 | |import scalatags.JsDom 1347 | | 1348 | |class ImageFactory(prefix: String) { 1349 | | import scalatags.JsDom.all._ 1350 | | def apply(name: String, altText: String, xs: Modifier*): JsDom.TypedTag[dom.html.Image] = { 1351 | | img(src := s"$$prefix/$$name", alt := altText, xs) 1352 | | } 1353 | |} 1354 | | 1355 | |object Image extends ImageFactory("assets/images") 1356 | |""".stripMargin) 1357 | } 1358 | } 1359 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/utils/FrontendPaths.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.utils 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.utils._ 6 | 7 | trait FrontendPaths { utils: UtilPaths => 8 | def assets(module: File): File = 9 | module.subFile(assetsPathPart) 10 | def images(module: File): File = 11 | module.subFile(imagesPathPart) 12 | def fonts(module: File): File = 13 | module.subFile(fontsPathPart) 14 | def indexDevHtml(module: File): File = 15 | new File(s"$module${File.separator}$assetsPathPart${File.separator}index.dev.html") 16 | def indexProdHtml(module: File): File = 17 | new File(s"$module${File.separator}$assetsPathPart${File.separator}index.prod.html") 18 | 19 | def viewsPackageInSrc(rootPackage: File): File = 20 | rootPackage.subFile("views") 21 | 22 | def stylesPackageInSrc(rootPackage: File): File = 23 | rootPackage.subFile("styles") 24 | 25 | private val assetsPathPart = Seq("src", "main", "assets").mkString(File.separator) 26 | private val imagesPathPart = Seq(assetsPathPart, "images").mkString(File.separator) 27 | private val fontsPathPart = Seq(assetsPathPart, "fonts").mkString(File.separator) 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/plugins/utils/UtilPaths.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.plugins.utils 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.GeneratorSettings 6 | import io.udash.generator.utils._ 7 | 8 | trait UtilPaths { 9 | def resources(module: File): File = 10 | module.subFile(resourcesPathPart) 11 | def src(module: File): File = 12 | module.subFile(srcPathPart) 13 | def testSrc(module: File): File = 14 | module.subFile(testSrcPathPart) 15 | def rootPackageInSrc(module: File, settings: GeneratorSettings): File = 16 | module.subFile(Seq(srcPathPart, packagePathPart(settings)).mkString(File.separator)) 17 | def rootPackageInTestSrc(module: File, settings: GeneratorSettings): File = 18 | module.subFile(Seq(testSrcPathPart, packagePathPart(settings)).mkString(File.separator)) 19 | 20 | private val resourcesPathPart = Seq("src", "main", "resources").mkString(File.separator) 21 | private val srcPathPart = Seq("src", "main", "scala").mkString(File.separator) 22 | private val testSrcPathPart = Seq("src", "test", "scala").mkString(File.separator) 23 | private def packagePathPart(settings: GeneratorSettings) = settings.rootPackage.mkString(File.separator) 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/utils/FileOps.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator.utils 2 | 3 | import java.io._ 4 | 5 | import io.udash.generator.plugins.Placeholder 6 | 7 | import scala.io.Source 8 | import scala.util.matching.Regex 9 | 10 | /** Basic file operations */ 11 | trait FileOps { 12 | /** Writes `content` into `file`. */ 13 | protected def writeFile(file: File)(content: String) = { 14 | new PrintWriter(file, "UTF-8") { 15 | write(content) 16 | close() 17 | } 18 | } 19 | 20 | /** Appends `content` into `file`. */ 21 | protected def appendFile(file: File)(content: String) = { 22 | new PrintWriter(new BufferedWriter(new FileWriter(file.getAbsolutePath, true))) { 23 | write(content) 24 | close() 25 | } 26 | } 27 | 28 | /** Replaces all parts of `file` matching `regex` with `replacement`. */ 29 | protected def replaceInFile(file: File)(regex: String, replacement: String) = { 30 | val current: String = readWholeFile(file) 31 | new PrintWriter(file, "UTF-8") { 32 | write(current.replaceAll(regex, replacement)) 33 | close() 34 | } 35 | } 36 | 37 | /** Adds `content` before all occurrences of `placeholder` in `file`. */ 38 | protected def appendOnPlaceholder(file: File)(placeholder: Placeholder, content: String) = 39 | replaceInFile(file)(Regex.quote(placeholder.toString), content+placeholder.toString) 40 | 41 | /** Removes all parts of `file` matching `regex`. */ 42 | protected def removeFromFile(file: File)(regex: String) = 43 | replaceInFile(file)(regex, "") 44 | 45 | protected def removeFileOrDir(file: File): Unit = { 46 | if (file.exists()) { 47 | if (file.isDirectory) { 48 | file.listFiles().foreach(removeFileOrDir) 49 | } 50 | file.delete() 51 | } 52 | } 53 | 54 | private def readWholeFile(file: File): String = 55 | Source.fromFile(file).getLines.mkString("\n") 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/scala/io/udash/generator/utils/package.scala: -------------------------------------------------------------------------------- 1 | package io.udash.generator 2 | 3 | import java.io.File 4 | 5 | import io.udash.generator.exceptions._ 6 | 7 | package object utils { 8 | def createFiles(files: Seq[File], requireNotExists: Boolean = false): Unit = { 9 | files.foreach((file: File) => { 10 | if (!file.createNewFile() && requireNotExists) throw FileCreationError(s"${file.getAbsolutePath} already exists!") 11 | }) 12 | } 13 | 14 | def createDirs(files: Seq[File], requireNotExists: Boolean = false): Unit = { 15 | files.foreach((file: File) => { 16 | if (!file.mkdirs() && requireNotExists) throw FileCreationError(s"${file.getAbsolutePath} already exists!") 17 | }) 18 | } 19 | 20 | def requireFilesExist(files: Seq[File]): Unit = { 21 | files.foreach((file: File) => { 22 | if (!file.exists()) throw FileDoesNotExist(s"${file.getAbsolutePath} does not exist!") 23 | }) 24 | } 25 | 26 | implicit class SeqOps(private val s: Seq[String]) extends AnyVal { 27 | def mkPackage(): String = s.mkString(".") 28 | } 29 | 30 | implicit class FileExt(private val file: File) extends AnyVal { 31 | def subFile(f: String): File = new File(s"${file.getAbsolutePath}${File.separator}$f") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/run.bat: -------------------------------------------------------------------------------- 1 | java -jar udash-generator.jar 2 | -------------------------------------------------------------------------------- /dist/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | java -jar udash-generator.jar -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.15 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1") -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Assembling Udash generator..." 4 | sbt assembly 5 | cp cmd/target/scala-2.12/udash-generator.jar dist/udash-generator.jar 6 | 7 | cd dist 8 | 9 | ERRORS=0 10 | for f in ../test/*.cnf; do 11 | echo "Starting test $f..." 12 | ./run.sh < $f > /dev/null 13 | cd test-app 14 | echo "Compiling $f..." 15 | sbt compile > ../$f.log 16 | if [ $? -eq 0 ]; then 17 | echo -e "Test $f \e[32msucceed\e[39m!" 18 | else 19 | echo -e "Test $f \e[31mfailed\e[39m!" 20 | ((ERRORS++)) 21 | fi 22 | cd .. 23 | done 24 | 25 | cd .. 26 | exit ${ERRORS} -------------------------------------------------------------------------------- /test/basic-front-only.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 1 7 | true 8 | false 9 | false 10 | false 11 | true -------------------------------------------------------------------------------- /test/basic-front.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | false 12 | false 13 | false 14 | false 15 | true -------------------------------------------------------------------------------- /test/demos-front-only.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 1 7 | true 8 | true 9 | true 10 | true 11 | true -------------------------------------------------------------------------------- /test/demos-front.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | true 12 | true 13 | false 14 | true 15 | true -------------------------------------------------------------------------------- /test/full-com-example.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | full-app 4 | com.example 5 | com.example.app 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | true 12 | true 13 | true 14 | true 15 | true 16 | true 17 | true -------------------------------------------------------------------------------- /test/full-custom-modules-retries.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | full app 4 | app!! 5 | full-app 6 | 7 | io.udash app.full 8 | 123.udash.app.full 9 | io.123udash.app.full 10 | io.udash.app.full .test 11 | 123.udash.app.full 12 | io.udash-app.full 13 | io.udash.app.full 14 | 2 15 | back 16 | share 17 | front 18 | true 19 | true 20 | true 21 | true 22 | true 23 | true 24 | false 25 | true -------------------------------------------------------------------------------- /test/full-custom-modules.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | full-app 4 | io.udash.app.full 5 | io.udash.app.full 6 | 2 7 | back 8 | share 9 | front 10 | true 11 | true 12 | true 13 | true 14 | true 15 | true 16 | false 17 | true -------------------------------------------------------------------------------- /test/full.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | full-app 4 | io.udash.app.full 5 | io.udash.app.full 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | true 12 | true 13 | true 14 | true 15 | true 16 | true 17 | true -------------------------------------------------------------------------------- /test/jetty.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | false 12 | false 13 | true 14 | false 15 | false 16 | true -------------------------------------------------------------------------------- /test/rpc.cnf: -------------------------------------------------------------------------------- 1 | test-app 2 | true 3 | empty-app 4 | io.udash.app 5 | io.udash.app 6 | 2 7 | backend 8 | shared 9 | frontend 10 | true 11 | false 12 | false 13 | true 14 | true 15 | true 16 | false 17 | true --------------------------------------------------------------------------------