svg.escobar.life

A simple SVG markup editor for the web

Commit
f6e575e795989ab7013871c6a6f6d4db05a55363
Parent
f62c6cb5683b638662387fbe711f3487f140d381
Author
Gark Garcia <37553739+GarkGarcia@users.noreply.github.com>
Date

Finished implementing syntax-highlighting.

Diffstat

5 files changed, 119 insertions, 73 deletions

Status File Name N° Changes Insertions Deletions
Added src/Editor.elm 54 54 0
Modified src/Main.elm 6 3 3
Modified src/Types.elm 9 3 6
Modified src/View.elm 37 2 35
Modified styles.css 86 57 29
diff --git a/src/Editor.elm b/src/Editor.elm
@@ -0,0 +1,53 @@
+module Editor exposing (editor)
+
+import Types exposing (..)
+import Html exposing (Html, Attribute, div, pre, code, text, textarea)
+import Html.Attributes exposing (id, value, style, spellcheck)
+import Html.Events exposing (onInput, on)
+import Json.Decode
+import SyntaxHighlight exposing (xml, toBlockHtml)
+
+editor : Model -> Html Msg
+editor model =
+    div [ id "editor" ]
+        [ div [ translate model.editorScroll ]
+            [ codeDisplay model ]
+        , textarea model
+        ]
+
+textarea : Model -> Html Msg
+textarea model =
+    Html.textarea
+        [ value model.image
+        , onInput Update
+        , onScroll Scroll
+        , spellcheck False
+        ] []
+
+codeDisplay : Model -> Html Msg
+codeDisplay model =
+    xml (if model.image == "" then placeholder else model.image)
+        |> Result.map (toBlockHtml Nothing)
+        |> Result.withDefault
+            (pre [] [ code [] [ text model.image ] ])
+
+onScroll : ((Int, Int) -> msg) -> Attribute msg
+onScroll f = 
+    on "scroll"
+        (Json.Decode.map2 (\x -> \y -> (x, y))
+            (Json.Decode.at [ "target", "scrollLeft" ] Json.Decode.int)
+            (Json.Decode.at [ "target", "scrollTop" ]  Json.Decode.int)
+            |> Json.Decode.map f
+        )
+
+transform : String -> Attribute msg
+transform str =
+    style "transform" str
+
+translate : (Int, Int) -> Attribute Msg
+translate (x, y) =
+    transform ("translate(" ++ (String.fromInt -x) ++ "px, " ++ (String.fromInt -y) ++ "px)")
+
+placeholder : String
+placeholder =
+    "<svg ...> ... </svg>"+
\ No newline at end of file
diff --git a/src/Main.elm b/src/Main.elm
@@ -44,8 +44,8 @@ update msg model =
         Upload upl ->
             upload model upl
 
-        Scroll offset ->
-            ({model | offset = offset}, Cmd.none)
+        Scroll scroll ->
+            ({model | editorScroll = scroll}, Cmd.none)
 
 load : Result Http.Error String -> Cmd Msg
 load result =
@@ -77,7 +77,7 @@ init =
     , darkMode = False
     , uriEncoder = Maybe.withDefault Regex.never <| Regex.fromString "!|#|\\$|%|&|'|\\(|\\)|\\*|\\+|,|\\/|:|;|=|\\?|@|\\[|\\]"
     , fileName = "example.svg"
-    , offset = { top = 0, left = 0 }
+    , editorScroll = (0, 0)
     }
 
 loadExample : Cmd Msg
diff --git a/src/Types.elm b/src/Types.elm
@@ -10,14 +10,11 @@ type alias Model =
     , darkMode : Bool
     , uriEncoder : Regex
     , fileName : String
-    , offset : Offset
+    , editorScroll : (Int, Int)
     }
 
 type Upload = Requested | Selected File
 
-type alias Offset =
-    { top : Int, left : Int }
-
 type Msg
     = Update String
     | Load (Result Http.Error String)
@@ -25,4 +22,4 @@ type Msg
     | DarkModeToggle
     | Download
     | Upload Upload
-    | Scroll Offset-
\ No newline at end of file
+    | Scroll (Int, Int)+
\ No newline at end of file
diff --git a/src/View.elm b/src/View.elm
@@ -1,6 +1,7 @@
 module View exposing (view)
 
 import Types exposing (..)
+import Editor exposing (editor)
 import Html exposing (Html, div, img, button, a, textarea, pre, code, text)
 import Html.Events exposing (onClick, onInput, on)
 import Html.Attributes exposing (id, class, src, href, value, style, spellcheck, placeholder, target, rel)
@@ -9,7 +10,6 @@ import Svg exposing (svg, path)
 import Svg.Attributes exposing (d, viewBox, fill)
 import Json.Decode
 import Regex exposing (Regex, Match, replace)
-import SyntaxHighlight exposing (useTheme, xml, oneDark, toBlockHtml)
 
 
 view : Model -> Document Msg
@@ -19,7 +19,7 @@ view model =
         [ div [ id "container", class (if model.isValid then "" else "error") ]
             [ div [ id "title" ] [ logo, githubLink ]
             , display model
-            , texteditor model
+            , editor model
             ]
         ]
     }
@@ -40,39 +40,6 @@ display model =
             [ if model.darkMode then sunIcon else moonIcon ]
         ]
 
-texteditor : Model -> Html Msg
-texteditor model =
-    div [ id "editor", onInput Update ]
-        [ useTheme oneDark
-        , xml (model.image ++ (String.fromInt model.offset.left))
-            |> Result.map (toBlockHtml Nothing)
-            |> Result.withDefault
-                (pre (translate model.offset)
-                    [ code [] [ text model.image ]]
-                )
-        , viewTextarea model
-        ]
-
-translate : Offset -> List (Html.Attribute Msg)
-translate { top, left } =
-    [ style "left" ((String.fromInt -top) ++ "px")
-    , style "top"  ((String.fromInt -left) ++ "px")
-    ]
-
-viewTextarea : Model -> Html Msg
-viewTextarea model =
-    textarea
-        [ value model.image
-        , onInput Update
-        , spellcheck False
-        , on "scroll"
-            (Json.Decode.map2 Offset
-                (Json.Decode.at [ "target", "scrollTop" ] Json.Decode.int)
-                (Json.Decode.at [ "target", "scrollLeft" ] Json.Decode.int)
-                |> Json.Decode.map Scroll
-            )
-        ] []
-
 uri : Model -> String
 uri model =
     "data:image/svg+xml;utf8," ++ (replace model.uriEncoder percentEscape model.image)
diff --git a/styles.css b/styles.css
@@ -14,10 +14,21 @@
     scrollbar-width: none; /* Firefox */
     -ms-overflow-style: none; /* IE + Edge */
 
+    transition: background-color .5s ease, color .5s ease;
+
     --background: #450e79;
-    --mid: #7b2c9b;
+    --selection: #3b3ee040;
     --light: #e0ebe0;
     --dark: #1c0531;
+
+    --hl1: #d19a66;
+    --hl2: #f3a47f;
+    --hl3: #f852a5;
+    --hl4: var(--hl3);
+    --hl5: #5da7fc;
+    --hl6: #d19a66;
+
+    --comm: #95ee61;
 }
 
 @media (pointer: fine) {
@@ -44,19 +55,6 @@
     width: 0px; /*Chrome + Safari + Opera*/
 }
 
-textarea::selection {
-    background: var(--mid);
-}
-
-textarea::-moz-selection {
-    background: var(--mid);
-}
-
-textarea::placeholder {
-    color: var(--light);
-    opacity: 1;
-}
-
 svg {
     fill: none;
     fill-rule: evenodd;
@@ -158,9 +156,16 @@ body > div#container, body > div#container > * {
 
 body > div#container.error {
     --background: #96162c;
-    --mid: #b33748;
-
+    --selection: #e03b5140;
     --light: #eec6df;
+
+    --hl1: #d17666;
+    --hl2: #f38d7f;
+    --hl3: #fd5b5b;
+    --hl5: #fc5db4;
+    --hl6: #d19a66;
+
+    --comm: #ee6180;
 }
 
 body > div#container img {
@@ -187,7 +192,6 @@ body > div#container > div#display {
     border-radius: var(--tiny);
 
     box-shadow: 0 0 calc(.25 * var(--small)) .5vmax #00000015;
-    transition: background .5s ease;
 }
 
 @media (pointer: fine), (pointer: coarse) and (orientation: landscape) {
@@ -249,15 +253,17 @@ body > div#container > div#editor {
 }
 
 body > div#container > div#editor * {
-    background: transparent !important;
-
-    font-family: 'B612 Mono', monospace !important;
-    font-style: italic !important;
+    font-family: 'B612 Mono', monospace;
 }
 
 body > div#container > div#editor > * {
     position: absolute;
+    will-change: transform;
+}
+
+body > div#container > div#editor > *, body > div#container > div#editor > * > * {
     margin: 0 !important;
+    font-size: 1rem !important;
 }
 
 body > div#container > div#editor > textarea {
@@ -269,16 +275,38 @@ body > div#container > div#editor > textarea {
     width: 100%;
     height: 100%;
 
-    margin: 0;
-
-    background: transparent;
     border: none;
-    
+    background: transparent;
 
-    font-family: 'B612 Mono', monospace;
     color: transparent;
-    font-style: italic;
+    caret-color: var(--light);
 
     scrollbar-width: none; /*For Firefox*/
     -ms-overflow-style: none; /*For IE and Edge*/
-}-
\ No newline at end of file
+}
+
+body > div#container > div#editor > textarea::selection {
+    background: var(--selection);
+}
+
+body > div#container > div#editor > textarea::-moz-selection {
+    background: var(--selection);
+}
+
+.elmsh {color: var(--light);}
+.elmsh-hl {background: rgba(229, 231,235, 0.1);}
+.elmsh-add {background: rgba(40, 124,82, 0.4);}
+.elmsh-del {background: rgba(136, 64,67, 0.4);}
+
+.elmsh-comm {
+    font-style: italic;
+    color: var(--comm);
+}
+
+.elmsh1 { color: var(--hl1); }
+.elmsh2 { color: var(--hl2); }
+.elmsh3 { color: var(--hl3); }
+.elmsh4 { color: var(--hl4); }
+.elmsh5 { color: var(--hl5); }
+.elmsh6 { color: var(--hl6); }
+.elmsh7 { color: var(--light); }+
\ No newline at end of file