├── sync-demo.sh
├── docs
├── 1
│ ├── prototypes
│ ├── self
│ ├── users
│ │ └── admin01
│ └── colors
├── admin01.png
├── services.png
├── index.html
├── style.css
└── demo.html
├── server
├── .gitignore
├── sql
│ ├── 1-create-schema.sql
│ ├── 7-flip-image-field.sql
│ ├── 6-temporary-floor.sql
│ ├── 4-utf8mb4.sql
│ ├── 5-add-object-field.sql
│ ├── 3-update-at.sql
│ ├── 8-objects_opt.sql
│ └── 2-create-tables.sql
├── defaultConfig.json
├── package.json
├── static
│ ├── filestorage.js
│ ├── template
│ │ ├── login.html
│ │ ├── master.html
│ │ └── index.html
│ └── generate-html.js
├── lib
│ ├── log.js
│ ├── config.js
│ ├── mock.js
│ ├── sql.js
│ ├── account-service.js
│ ├── search-optimizer.js
│ ├── schema.js
│ ├── mysql.js
│ └── profile-service.js
├── initialize.sh
└── commands.js
├── .travis.yml
├── src
├── elm
│ ├── API
│ │ ├── Page.elm
│ │ └── Cache.elm
│ ├── Page
│ │ ├── Map
│ │ │ ├── ContextMenuContext.elm
│ │ │ ├── Main.elm
│ │ │ ├── LinkCopy.elm
│ │ │ ├── GridLayer.elm
│ │ │ ├── FloorUpdateInfoView.elm
│ │ │ ├── PrintGuide.elm
│ │ │ ├── URL.elm
│ │ │ ├── PrototypePreviewView.elm
│ │ │ ├── ClipboardOptionsView.elm
│ │ │ ├── KeyOperation.elm
│ │ │ ├── ContextMenu.elm
│ │ │ ├── SearchResultItemView.elm
│ │ │ ├── Msg.elm
│ │ │ └── FloorsInfoView.elm
│ │ ├── Master
│ │ │ ├── Styles.elm
│ │ │ ├── Main.elm
│ │ │ ├── Model.elm
│ │ │ ├── Msg.elm
│ │ │ └── PrototypeForm.elm
│ │ └── Login
│ │ │ ├── Styles.elm
│ │ │ └── Main.elm
│ ├── Util
│ │ ├── DictUtil.elm
│ │ ├── StringUtil.elm
│ │ ├── DecodeUtil.elm
│ │ ├── StyleUtil.elm
│ │ ├── IdGenerator.elm
│ │ ├── UrlParser.elm
│ │ ├── File.elm
│ │ ├── ListUtil.elm
│ │ ├── UUID.elm
│ │ ├── UndoList.elm
│ │ └── HttpUtil.elm
│ ├── Model
│ │ ├── Errors.elm
│ │ ├── Person.elm
│ │ ├── Prototype.elm
│ │ ├── User.elm
│ │ ├── SaveRequest.elm
│ │ ├── ColorPalette.elm
│ │ ├── Scale.elm
│ │ ├── ProfilePopupLogic.elm
│ │ ├── DateFormatter.elm
│ │ ├── Mode.elm
│ │ ├── FloorInfo.elm
│ │ ├── ObjectsChange.elm
│ │ ├── EditingFloor.elm
│ │ ├── SearchResult.elm
│ │ ├── FloorDiff.elm
│ │ └── Prototypes.elm
│ ├── Native
│ │ ├── HtmlUtil.js
│ │ ├── HttpUtil.js
│ │ ├── Emoji.js
│ │ ├── File.js
│ │ └── ClipboardData.js
│ ├── View
│ │ ├── MessageBar.elm
│ │ ├── HeaderView.elm
│ │ ├── SearchInputView.elm
│ │ ├── DialogView.elm
│ │ ├── Common.elm
│ │ ├── MessageBarForMainView.elm
│ │ ├── PrototypePreviewView.elm
│ │ ├── Icons.elm
│ │ ├── CommonStyles.elm
│ │ └── Theme.elm
│ ├── Component
│ │ ├── ImageLoader.elm
│ │ ├── Dialog.elm
│ │ └── FloorDeleter.elm
│ └── CoreType.elm
├── template
│ ├── login.html
│ ├── master.html
│ └── index.html
└── style.css
├── .gitignore
├── README.md
├── package.json
├── elm-package.json
├── persistent-cache
└── src
│ ├── LocalStorage.elm
│ ├── Native
│ └── LocalStorage.js
│ └── Dag.elm
├── watch.js
└── Spec.md
/sync-demo.sh:
--------------------------------------------------------------------------------
1 | mv index.js docs/demo.js
2 |
--------------------------------------------------------------------------------
/docs/admin01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorksApplications/office-maker/HEAD/docs/admin01.png
--------------------------------------------------------------------------------
/docs/services.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WorksApplications/office-maker/HEAD/docs/services.png
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules
3 |
4 | public
5 | template
6 | secret
7 | config.json
8 |
--------------------------------------------------------------------------------
/server/sql/1-create-schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA map2 DEFAULT CHARACTER SET utf8;
2 |
3 | SET SQL_SAFE_UPDATES=0;
4 |
--------------------------------------------------------------------------------
/server/sql/7-flip-image-field.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE `map2`.`floors`
2 | ADD COLUMN `flipImage` TINYINT(1) NOT NULL AFTER `image`;
3 |
--------------------------------------------------------------------------------
/docs/1/prototypes:
--------------------------------------------------------------------------------
1 | [{"id":"1","tenantId":"","name":"","width":56,"height":96,"backgroundColor":"#eda","color":"#000","fontSize":14,"shape":"rectangle"}]
2 |
--------------------------------------------------------------------------------
/server/sql/6-temporary-floor.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE `map2`.`floors`
2 | DROP COLUMN `public`,
3 | ADD COLUMN `temporary` TINYINT(1) NOT NULL AFTER `realHeight`;
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "4.2"
4 | before_script:
5 | - npm install -g elm
6 | - elm-package install -y
7 | script: sh build.sh
8 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
Office Maker
2 |
3 | Architecture
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/server/sql/4-utf8mb4.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE map2.objects CHARACTER SET = utf8mb4;
2 | ALTER SCHEMA map2 DEFAULT CHARACTER SET utf8mb4;
3 | -- also need to modify sever configuration
4 |
--------------------------------------------------------------------------------
/server/sql/5-add-object-field.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE `map2`.`objects`
2 | ADD COLUMN `bold` TINYINT(1) NOT NULL AFTER `shape`,
3 | ADD COLUMN `url` VARCHAR(1024) NOT NULL AFTER `bold`;
4 |
--------------------------------------------------------------------------------
/docs/1/self:
--------------------------------------------------------------------------------
1 | {"id":"admin01","pass":"","role":"admin","personId":"admin01","tenantId":"","person":{"id":"admin01","empNo":"1234","name":"Admin01","org":"Sample Co.,Ltd","tel":null,"mail":"admin01@example.com","image":"./admin01.png"}}
2 |
--------------------------------------------------------------------------------
/src/elm/API/Page.elm:
--------------------------------------------------------------------------------
1 | module API.Page exposing (..)
2 |
3 |
4 | top : String
5 | top =
6 | "."
7 |
8 |
9 | login : String
10 | login =
11 | "./login"
12 |
13 |
14 | master : String
15 | master =
16 | "./master"
17 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/ContextMenuContext.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.ContextMenuContext exposing (..)
2 |
3 | import CoreType exposing (..)
4 |
5 |
6 | type ContextMenuContext
7 | = ObjectContextMenu ObjectId
8 | | FloorInfoContextMenu FloorId
9 |
--------------------------------------------------------------------------------
/docs/1/users/admin01:
--------------------------------------------------------------------------------
1 | {"id":"admin01","pass":"","role":"admin","personId":"admin01","tenantId":"","person":{"id":"admin01","empNo":"1234","name":"Admin01","org":"Sample Co.,Ltd","tel":null,"mail":"admin01@xxx.com","image":"images/users/admin01.png"}}
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | elm-stuff
2 | node_modules
3 | elm.js
4 |
5 | repl-temp-*
6 | *.ods*
7 | npm-debug.log
8 |
9 | count.sh
10 |
11 | aws-config
12 | dynamodb_local
13 |
14 | people-register-service
15 |
16 | account-service
17 | profile-service
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Office Maker
2 | ====
3 |
4 | Now we are making a new version.
5 |
6 | If you're looking for Elm code, it now lives [here](https://github.com/WorksApplications/office-maker-client).
7 |
8 |
9 | ## License
10 |
11 | [Apache License 2.0](LICENSE)
12 |
--------------------------------------------------------------------------------
/src/elm/Page/Master/Styles.elm:
--------------------------------------------------------------------------------
1 | module Page.Master.Styles exposing (..)
2 |
3 | import View.CommonStyles as Styles
4 |
5 |
6 | type alias S =
7 | List ( String, String )
8 |
9 |
10 | validationError : S
11 | validationError =
12 | [ ( "color", Styles.errorTextColor )
13 | ]
14 |
--------------------------------------------------------------------------------
/src/elm/Util/DictUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.DictUtil exposing (..)
2 |
3 | import Dict exposing (Dict)
4 |
5 |
6 | addAll : (a -> comparable) -> List a -> Dict comparable a -> Dict comparable a
7 | addAll toKey list dict =
8 | List.foldl (\a d -> Dict.update (toKey a) (always (Just a)) d) dict list
9 |
--------------------------------------------------------------------------------
/server/sql/3-update-at.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE map2.objects
2 | ADD COLUMN updateAt BIGINT(20) NOT NULL AFTER floorVersion;
3 |
4 | ALTER TABLE map2.objects
5 | DROP COLUMN modifiedVersion;
6 |
7 | SELECT * FROM map2.floors ORDER BY id, version DESC;
8 | SELECT * FROM map2.objects ORDER BY id, floorId, floorVersion DESC;
9 |
--------------------------------------------------------------------------------
/docs/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: Roboto;
3 | }
4 |
5 | body {
6 | margin : 0;
7 | overflow-y: hidden;
8 | font-family: 'Roboto', sans-serif;
9 | }
10 |
11 | ul {
12 | list-style-type: none;
13 | padding-left: 0;
14 | margin : 0;
15 | }
16 |
17 | a {
18 | text-decoration: none;
19 | color: inherit;
20 | }
21 |
--------------------------------------------------------------------------------
/src/elm/Model/Errors.elm:
--------------------------------------------------------------------------------
1 | module Model.Errors exposing (..)
2 |
3 | import Dom
4 | import Util.File as File
5 | import API.API as API exposing (..)
6 |
7 |
8 | type GlobalError
9 | = APIError API.Error
10 | | FileError File.Error
11 | | HtmlError Dom.Error
12 | | PasteError String
13 | | Success String
14 | | NoError
15 |
--------------------------------------------------------------------------------
/src/elm/Model/Person.elm:
--------------------------------------------------------------------------------
1 | module Model.Person exposing (..)
2 |
3 |
4 | type alias Id =
5 | String
6 |
7 |
8 | type alias Person =
9 | { id : Id
10 | , name : String
11 | , post : String
12 | , mail : Maybe String
13 | , tel1 : Maybe String
14 | , tel2 : Maybe String
15 | , image : Maybe String
16 | }
17 |
--------------------------------------------------------------------------------
/src/elm/Util/StringUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.StringUtil exposing (..)
2 |
3 |
4 | split2 : String -> String -> Maybe ( String, String )
5 | split2 separator s =
6 | case String.indices separator s of
7 | head :: tail ->
8 | Just ( String.slice 0 head s, String.dropLeft (head + 1) s )
9 |
10 | _ ->
11 | Nothing
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "office-maker",
3 | "version": "1.0.0",
4 | "description": "Office Maker\r ----",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha test/test.js"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "minimatch": "^3.0.0",
13 | "slash": "^1.0.0",
14 | "watch": "^0.17.1"
15 | },
16 | "dependencies": {
17 | "ejs": "^2.5.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/elm/Model/Prototype.elm:
--------------------------------------------------------------------------------
1 | module Model.Prototype exposing (..)
2 |
3 | import Model.Object exposing (Shape)
4 |
5 |
6 | type alias Id =
7 | String
8 |
9 |
10 | type alias Prototype =
11 | { id : Id
12 | , name : String
13 | , color : String
14 | , backgroundColor : String
15 | , width : Int
16 | , height : Int
17 | , fontSize : Float
18 | , shape : Shape
19 | , personId : Maybe String
20 | }
21 |
--------------------------------------------------------------------------------
/server/defaultConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "mysql": {
3 | "host": "localhost",
4 | "user": "root",
5 | "pass": ""
6 | },
7 | "title": "Office Map",
8 | "accountServiceRoot": "http://localhost/accounts/api",
9 | "profileServiceRoot": "http://localhost/profiles/api",
10 | "secret": "path/to/pubkey.pem",
11 | "multiTenency": false,
12 | "log": {
13 | "access": "INFO",
14 | "system": "INFO",
15 | "error": "INFO"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/elm/Native/HtmlUtil.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_HtmlUtil = function(localRuntime) {
2 | var canvas = document.createElement('canvas');
3 | var context = canvas.getContext('2d');
4 |
5 | function measureText(fontFamily, fontSize, s) {
6 | context.font = fontSize + "px '" + fontFamily + "'";
7 | var metrics = context.measureText(s);
8 | return metrics.width;
9 | }
10 | return {
11 | measureText: F3(measureText)
12 | };
13 | }();
14 |
--------------------------------------------------------------------------------
/src/elm/Util/DecodeUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.DecodeUtil exposing (..)
2 |
3 | import Json.Decode as D exposing (Decoder)
4 | import Json.Decode.Pipeline as Pipeline
5 |
6 |
7 | tuple2 : (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c
8 | tuple2 f a b =
9 | D.map2 f (D.index 0 a) (D.index 1 b)
10 |
11 |
12 |
13 | -- for Pipeline
14 |
15 |
16 | optional_ : String -> Decoder a -> Decoder (Maybe a -> b) -> Decoder b
17 | optional_ key decoder =
18 | Pipeline.custom (D.maybe (D.field key decoder))
19 |
--------------------------------------------------------------------------------
/src/elm/View/MessageBar.elm:
--------------------------------------------------------------------------------
1 | module View.MessageBar exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import View.Styles as Styles
6 |
7 |
8 | none : Html msg
9 | none =
10 | div [ style Styles.noneBar ] []
11 |
12 |
13 | success : String -> Html msg
14 | success msg =
15 | div [ style Styles.successBar ] [ text msg ]
16 |
17 |
18 | error : String -> Html msg
19 | error message =
20 | div [ class "message-bar-error", style Styles.errorBar ] [ text message ]
21 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/Main.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.Main exposing (..)
2 |
3 | import Navigation
4 | import Page.Map.Model exposing (Model)
5 | import Page.Map.Update as Update exposing (Flags)
6 | import Page.Map.View as View
7 | import Page.Map.Msg exposing (Msg)
8 | import Html.Lazy exposing (lazy)
9 |
10 |
11 | main : Program Flags Model Msg
12 | main =
13 | Navigation.programWithFlags Update.parseURL
14 | { init = Update.init
15 | , view = lazy View.view
16 | , update = Update.update
17 | , subscriptions = Update.subscriptions
18 | }
19 |
--------------------------------------------------------------------------------
/src/elm/Page/Master/Main.elm:
--------------------------------------------------------------------------------
1 | port module Page.Master.Main exposing (..)
2 |
3 | import Html
4 | import Page.Master.Model exposing (Model)
5 | import Page.Master.Msg exposing (Msg)
6 | import Page.Master.Update exposing (Flags, init, update, subscriptions)
7 | import Page.Master.View exposing (view)
8 |
9 |
10 | port removeToken : {} -> Cmd msg
11 |
12 |
13 | port tokenRemoved : ({} -> msg) -> Sub msg
14 |
15 |
16 | main : Program Flags Model Msg
17 | main =
18 | Html.programWithFlags
19 | { init = init
20 | , view = view
21 | , update = update removeToken
22 | , subscriptions = subscriptions
23 | }
24 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "office-maker-server",
3 | "version": "1.0.0",
4 | "description": "Office Maker server implementation",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "mocha test/test.js"
8 | },
9 | "author": "",
10 | "license": "index.js",
11 | "dependencies": {
12 | "body-parser": "^1.15.0",
13 | "express": "^4.13.4",
14 | "express-session": "^1.13.0",
15 | "fs-extra": "^0.28.0",
16 | "jsonwebtoken": "^7.1.9",
17 | "log4js": "^0.6.38",
18 | "minimatch": "^3.0.0",
19 | "mysql": "^2.10.2",
20 | "request": "^2.72.0",
21 | "slash": "^1.0.0",
22 | "uuid": "^2.0.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/elm/Model/User.elm:
--------------------------------------------------------------------------------
1 | module Model.User exposing (..)
2 |
3 | import Model.Person exposing (Person)
4 |
5 |
6 | type alias Id =
7 | String
8 |
9 |
10 | type User
11 | = Admin Person
12 | | General Person
13 | | Guest
14 |
15 |
16 | isAdmin : User -> Bool
17 | isAdmin user =
18 | case user of
19 | Admin _ ->
20 | True
21 |
22 | _ ->
23 | False
24 |
25 |
26 | isGuest : User -> Bool
27 | isGuest user =
28 | user == Guest
29 |
30 |
31 | admin : Person -> User
32 | admin person =
33 | Admin person
34 |
35 |
36 | general : Person -> User
37 | general person =
38 | General person
39 |
40 |
41 | guest : User
42 | guest =
43 | Guest
44 |
--------------------------------------------------------------------------------
/server/static/filestorage.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs-extra');
2 |
3 | var publicDir = __dirname + '/public';
4 |
5 | function save(path, image) {
6 | return new Promise((resolve, reject) => {
7 | fs.writeFile(publicDir + '/' + path, image, (e) => {
8 | if (e) {
9 | reject(e);
10 | } else {
11 | resolve();
12 | }
13 | });
14 | });
15 | }
16 |
17 | function empty(dir) {
18 | return new Promise((resolve, reject) => {
19 | fs.emptyDir(publicDir + '/' + dir, (e) => {
20 | if (e) {
21 | reject(e);
22 | } else {
23 | resolve();
24 | }
25 | });
26 | });
27 | }
28 |
29 | module.exports = {
30 | save: save,
31 | empty: empty,
32 | publicDir: publicDir
33 | };
34 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/LinkCopy.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.LinkCopy exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Events exposing (..)
6 | import View.CommonStyles as CS
7 |
8 |
9 | -- copy : Msg -> Cmd msg
10 | -- copy (Copy inputId) =
11 | -- copyLink inputId
12 |
13 |
14 | inputId : String
15 | inputId =
16 | "copy-link-input"
17 |
18 |
19 | view : String -> Html msg
20 | view url =
21 | div [ style styles ]
22 | [ input [ id inputId, style CS.input, value url ] []
23 | , button
24 | [ style CS.button
25 |
26 | -- , onClick (toMsg (Copy inputId))
27 | ]
28 | [ text "Copy" ]
29 | ]
30 |
31 |
32 | styles : List ( String, String )
33 | styles =
34 | []
35 |
--------------------------------------------------------------------------------
/src/elm/Util/StyleUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.StyleUtil exposing (..)
2 |
3 |
4 | type alias Style =
5 | List ( String, String )
6 |
7 |
8 | px : number -> String
9 | px num =
10 | toString num ++ "px"
11 |
12 |
13 | em : number -> String
14 | em num =
15 | toString num ++ "em"
16 |
17 |
18 | ch : number -> String
19 | ch num =
20 | toString num ++ "ch"
21 |
22 |
23 | percent : number -> String
24 | percent num =
25 | toString num ++ "%"
26 |
27 |
28 | rgb : number -> number -> number -> String
29 | rgb r g b =
30 | "rgb(" ++ toString r ++ "," ++ toString g ++ "," ++ toString b ++ ")"
31 |
32 |
33 | rgba : number -> number -> number -> number -> String
34 | rgba r g b a =
35 | "rgba(" ++ toString r ++ "," ++ toString g ++ "," ++ toString b ++ "," ++ toString a ++ ")"
36 |
--------------------------------------------------------------------------------
/src/elm/Native/HttpUtil.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_HttpUtil = function(localRuntime) {
2 | function sendFile(method, url, headers, file) {
3 | return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
4 | var xhr = new XMLHttpRequest();
5 | xhr.open(method, url, true);
6 | headers.forEach(function(header) {
7 | xhr.setRequestHeader(header[0], header[1]);
8 | });
9 | xhr.onload = function(e) {
10 | if (this.status == 200) {
11 | callback(_elm_lang$core$Native_Scheduler.succeed(_elm_lang$core$Native_Utils.Tuple0))
12 | } else {
13 | callback(_elm_lang$core$Native_Scheduler.fail("")) //TODO
14 | }
15 | };
16 | xhr.send(file);
17 | });
18 | }
19 | return {
20 | sendFile: F4(sendFile)
21 | };
22 | }();
23 |
--------------------------------------------------------------------------------
/src/elm/Util/IdGenerator.elm:
--------------------------------------------------------------------------------
1 | module Util.IdGenerator exposing (..)
2 |
3 | import Util.UUID as UUID
4 |
5 |
6 | type Seed
7 | = Seed UUID.Seed
8 |
9 |
10 | init : ( Int, Int ) -> Seed
11 | init ( i1, i2 ) =
12 | Seed (UUID.init i1 i2)
13 |
14 |
15 | new : Seed -> ( String, Seed )
16 | new (Seed seed) =
17 | let
18 | ( newValue, newSeed ) =
19 | UUID.step seed
20 | in
21 | ( newValue, Seed newSeed )
22 |
23 |
24 | zipWithNewIds : Seed -> List a -> ( List ( a, String ), Seed )
25 | zipWithNewIds seed list =
26 | List.foldr
27 | (\a ( list, seed ) ->
28 | let
29 | ( newId, newSeed ) =
30 | new seed
31 | in
32 | ( ( a, newId ) :: list, newSeed )
33 | )
34 | ( [], seed )
35 | list
36 |
--------------------------------------------------------------------------------
/src/elm/Native/Emoji.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_Emoji = function(localRuntime) {
2 | function splitText(s) {
3 | var node = document.createElement('div');
4 | node.textContent = s;
5 | twemoji.parse(node);
6 | var result = [];
7 | for (var i = 0; i < node.childNodes.length; i++) {
8 | var child = node.childNodes[i];
9 | if (child.tagName === 'IMG') {
10 | result.push({
11 | type: 'image',
12 | original: child.getAttribute('alt'),
13 | url: child.getAttribute('src')
14 | });
15 | } else {
16 | result.push({
17 | type: 'text',
18 | value: child.nodeValue
19 | });
20 | }
21 | }
22 | return _elm_lang$core$Native_List.fromArray(result);
23 | }
24 | return {
25 | splitText: splitText
26 | };
27 | }();
28 |
--------------------------------------------------------------------------------
/src/elm/Page/Master/Model.elm:
--------------------------------------------------------------------------------
1 | module Page.Master.Model exposing (..)
2 |
3 | import Debounce exposing (Debounce)
4 | import Component.Header as Header
5 | import Model.User as User exposing (User)
6 | import Model.I18n as I18n exposing (Language(..))
7 | import Model.Prototype exposing (Prototype)
8 | import Model.ColorPalette as ColorPalette exposing (ColorPalette)
9 | import API.API as API
10 | import Page.Master.PrototypeForm exposing (PrototypeForm)
11 |
12 |
13 | type alias Model =
14 | { apiConfig : API.Config
15 | , title : String
16 | , allAdmins : List User
17 | , colorPalette : ColorPalette
18 | , prototypes : List PrototypeForm
19 | , error : Maybe String
20 | , header : Header.Model
21 | , lang : Language
22 | , saveColorDebounce : Debounce ColorPalette
23 | , savePrototypeDebounce : Debounce Prototype
24 | }
25 |
--------------------------------------------------------------------------------
/src/elm/View/HeaderView.elm:
--------------------------------------------------------------------------------
1 | module View.HeaderView exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Lazy as Lazy
6 | import View.Styles as S
7 |
8 |
9 | view : Bool -> String -> Maybe String -> Html msg -> Html msg
10 | view printMode title link menu =
11 | header
12 | [ style (S.header printMode)
13 | ]
14 | [ h1
15 | [ style S.h1 ]
16 | [ case link of
17 | Just url ->
18 | Lazy.lazy2 headerLink url title
19 |
20 | Nothing ->
21 | text title
22 | ]
23 | , menu
24 | ]
25 |
26 |
27 | headerLink : String -> String -> Html msg
28 | headerLink url text_ =
29 | a
30 | [ style S.headerLink
31 | , href url
32 | ]
33 | [ text text_ ]
34 |
--------------------------------------------------------------------------------
/src/elm/Page/Master/Msg.elm:
--------------------------------------------------------------------------------
1 | module Page.Master.Msg exposing (..)
2 |
3 | import Http
4 | import Debounce exposing (Debounce)
5 | import API.Cache as Cache exposing (Cache, UserState)
6 | import Component.Header as Header
7 | import Model.User as User exposing (User)
8 | import Model.Prototype exposing (Prototype)
9 | import Model.ColorPalette as ColorPalette exposing (ColorPalette)
10 | import Page.Master.PrototypeForm exposing (PrototypeForm)
11 |
12 |
13 | type Msg
14 | = NoOp
15 | | Loaded UserState User ColorPalette (List Prototype) (List User)
16 | | HeaderMsg Header.Msg
17 | | AddColor Bool
18 | | DeleteColor Bool Int
19 | | InputColor Bool Int String
20 | | UpdatePrototype Int PrototypeForm
21 | | SaveColorDebounceMsg Debounce.Msg
22 | | SavePrototypeDebounceMsg Debounce.Msg
23 | | NotAuthorized
24 | | APIError Http.Error
25 |
--------------------------------------------------------------------------------
/server/lib/log.js:
--------------------------------------------------------------------------------
1 | var log4js = require('log4js');
2 | var fs = require('fs');
3 | var config = require('./config.js');
4 |
5 | var options = {
6 | appenders: [{
7 | category: "access",
8 | type: "dateFile",
9 | filename: "/tmp/access.log",
10 | pattern: "-yyyy-MM-dd",
11 | backups: 3
12 | }, {
13 | category: "system",
14 | type: "dateFile",
15 | filename: "/tmp/system.log",
16 | pattern: "-yyyy-MM-dd",
17 | backups: 3
18 | }],
19 | levels: config.log
20 | };
21 | var debug = true;
22 | if (debug) {
23 | options.appenders.forEach(appender => {
24 | appender.type = 'console';
25 | });
26 | }
27 |
28 | log4js.configure(options);
29 |
30 | module.exports = {
31 | access: log4js.getLogger('access'),
32 | system: log4js.getLogger('system'),
33 | express: log4js.connectLogger(log4js.getLogger('access'), {
34 | level: log4js.levels.INFO
35 | })
36 | };
37 |
--------------------------------------------------------------------------------
/src/template/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/server/sql/8-objects_opt.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE map2.objects_opt (
2 | id VARCHAR(36) NOT NULL,
3 | type VARCHAR(16) NOT NULL,
4 | name VARCHAR(128) NOT NULL,
5 | x INT NOT NULL,
6 | y INT NOT NULL,
7 | width INT NOT NULL,
8 | height INT NOT NULL,
9 | backgroundColor VARCHAR(64) NOT NULL,
10 | color VARCHAR(64) NOT NULL,
11 | fontSize DECIMAL(4,1) NOT NULL,
12 | shape VARCHAR(64) NOT NULL,
13 | bold TINYINT(1) NOT NULL,
14 | url VARCHAR(1024) NOT NULL,
15 | personId VARCHAR(128),
16 | personName VARCHAR(128) NOT NULL,
17 | personEmpNo VARCHAR(32) NOT NULL,
18 | personPost VARCHAR(512) NOT NULL,
19 | personTel1 VARCHAR(32) NOT NULL,
20 | personTel2 VARCHAR(32) NOT NULL,
21 | personMail VARCHAR(128) NOT NULL,
22 | personImage VARCHAR(256) NOT NULL,
23 | floorId VARCHAR(36) NOT NULL,
24 | editing TINYINT(1) NOT NULL,
25 | updateAt BIGINT(20) NOT NULL,
26 | UNIQUE(id, floorId, editing)
27 | );
28 |
--------------------------------------------------------------------------------
/server/static/template/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/server/lib/config.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 |
4 | /* paths */
5 | var configJsonPath = path.resolve(__dirname, '../config.json');
6 | var defaultConfigJsonPath = path.resolve(__dirname, '../defaultConfig.json');
7 | var replaceSecret = config => {
8 | var secretFilePath = path.resolve(__dirname, '..', config.secret);
9 | if (fs.existsSync(secretFilePath)) {
10 | config.secret = {
11 | token: fs.readFileSync(secretFilePath, 'utf8')
12 | };
13 | } else {
14 | // It is okay on test.
15 | config.secret = null;
16 | }
17 | };
18 |
19 | /* load */
20 | var config = null;
21 | if (fs.existsSync(configJsonPath)) {
22 | config = JSON.parse(fs.readFileSync(configJsonPath, 'utf8'));
23 | } else {
24 | config = JSON.parse(fs.readFileSync(defaultConfigJsonPath, 'utf8'));
25 | }
26 |
27 | /* additional/replace */
28 | replaceSecret(config);
29 | config.apiRoot = '/api';
30 |
31 | module.exports = config;
32 |
--------------------------------------------------------------------------------
/server/initialize.sh:
--------------------------------------------------------------------------------
1 | cd `dirname $0`
2 |
3 | user=root
4 | pass=root
5 |
6 | if ls config.json ; then
7 | echo "Already initialized. To reinitialize, delete config.json."
8 | exit 1;
9 | fi
10 |
11 | cat defaultConfig.json \
12 | | sed -e "s/\"user\": \"root\"/\"user\": \"$user\"/"\
13 | | sed -e "s/\"pass\": \"\"/\"pass\": \"$pass\"/" \
14 | > config.json
15 | mysql --user=$user --password=$pass -e "drop database map2;"
16 | mysql --user=$user --password=$pass < sql/1-create-schema.sql
17 | mysql --user=$user --password=$pass < sql/2-create-tables.sql
18 | mysql --user=$user --password=$pass < sql/3-update-at.sql
19 | mysql --user=$user --password=$pass < sql/4-utf8mb4.sql
20 | mysql --user=$user --password=$pass < sql/5-add-object-field.sql
21 | mysql --user=$user --password=$pass < sql/6-temporary-floor.sql
22 | mysql --user=$user --password=$pass < sql/7-flip-image-field.sql
23 | mysql --user=$user --password=$pass < sql/8-objects_opt.sql
24 | node commands.js createInitialData
25 |
--------------------------------------------------------------------------------
/src/template/master.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/server/static/template/master.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/elm/Native/File.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_File = function(localRuntime) {
2 |
3 | function readAsDataURL(file) {
4 | return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
5 | var reader = new FileReader();
6 | reader.readAsDataURL(file);
7 | reader.onload = function() {
8 | var dataUrl = reader.result;
9 | callback(_elm_lang$core$Native_Scheduler.succeed(dataUrl));
10 | };
11 | reader.onerror = function() {
12 | callback(_elm_lang$core$Native_Scheduler.succeed("")); //TODO
13 | };
14 | });
15 | }
16 |
17 | function getSizeOfImage(dataUrl) {
18 | var image = new Image();
19 | image.src = dataUrl;
20 | return _elm_lang$core$Native_Utils.Tuple2(image.width, image.height);
21 | }
22 |
23 | function length(fileList) {
24 | return fileList.length;
25 | }
26 |
27 | function getAt(i, fileList) {
28 | return fileList[i];
29 | }
30 | return {
31 | readAsDataURL: readAsDataURL,
32 | getSizeOfImage: getSizeOfImage,
33 | length: length,
34 | getAt: F2(getAt)
35 | };
36 | }();
37 |
--------------------------------------------------------------------------------
/src/elm/Util/UrlParser.elm:
--------------------------------------------------------------------------------
1 | module Util.UrlParser exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Util.StringUtil exposing (..)
5 | import Http
6 |
7 |
8 | type alias URL =
9 | ( List String, Dict String String )
10 |
11 |
12 | parseSearch : String -> Dict String String
13 | parseSearch s_ =
14 | let
15 | s =
16 | String.dropLeft 1 s_
17 |
18 | -- drop "?"
19 | list =
20 | String.split "&" s
21 |
22 | keyValue indices =
23 | case indices of
24 | head :: tail ->
25 | Just ( String.slice 0 head, String.dropLeft (head + 1) )
26 |
27 | _ ->
28 | Nothing
29 |
30 | maybeKeyValues =
31 | List.map (split2 "=") list
32 |
33 | updateDict maybe dict =
34 | case maybe of
35 | Just ( key, value ) ->
36 | Dict.insert key (Http.decodeUri value |> Maybe.withDefault "") dict
37 |
38 | Nothing ->
39 | dict
40 | in
41 | List.foldl updateDict Dict.empty maybeKeyValues
42 |
--------------------------------------------------------------------------------
/src/elm/Page/Login/Styles.elm:
--------------------------------------------------------------------------------
1 | module Page.Login.Styles exposing (..)
2 |
3 | import View.CommonStyles as Styles
4 |
5 |
6 | type alias S =
7 | List ( String, String )
8 |
9 |
10 | loginContainer : S
11 | loginContainer =
12 | [ ( "margin-left", "auto" )
13 | , ( "margin-right", "auto" )
14 | , ( "margin-top", "40px" )
15 | , ( "margin-bottom", "auto" )
16 | , ( "width", "400px" )
17 | , ( "padding", "15px" )
18 | , ( "border", "solid 1px #aaa" )
19 | ]
20 |
21 |
22 | loginCaption : S
23 | loginCaption =
24 | []
25 |
26 |
27 | loginError : S
28 | loginError =
29 | [ ( "color", Styles.errorTextColor )
30 | , ( "margin-bottom", "15px" )
31 | ]
32 |
33 |
34 | loginSubmitButton : S
35 | loginSubmitButton =
36 | Styles.primaryButton ++ [ ( "margin-top", "20px" ), ( "width", "100%" ) ]
37 |
38 |
39 | formInput : S
40 | formInput =
41 | Styles.input
42 | ++ [ ( "padding", "7px 8px" )
43 | , ( "vertical-align", "middle" )
44 | , ( "font-size", "13px" )
45 | , ( "margin-top", "5px" )
46 | , ( "margin-bottom", "15px" )
47 | ]
48 |
--------------------------------------------------------------------------------
/docs/1/colors:
--------------------------------------------------------------------------------
1 | [{"id":"0","tenantId":"","ord":0,"type":"backgroundColor","color":"#eda"},{"id":"1","tenantId":"","ord":1,"type":"backgroundColor","color":"#baf"},{"id":"10","tenantId":"","ord":10,"type":"color","color":"#75a"},{"id":"11","tenantId":"","ord":11,"type":"color","color":"#c57"},{"id":"12","tenantId":"","ord":12,"type":"color","color":"#69a"},{"id":"13","tenantId":"","ord":13,"type":"color","color":"#8c5"},{"id":"14","tenantId":"","ord":14,"type":"color","color":"#5ab"},{"id":"15","tenantId":"","ord":15,"type":"color","color":"#666"},{"id":"16","tenantId":"","ord":16,"type":"color","color":"#000"},{"id":"2","tenantId":"","ord":2,"type":"backgroundColor","color":"#fba"},{"id":"3","tenantId":"","ord":3,"type":"backgroundColor","color":"#9bd"},{"id":"4","tenantId":"","ord":4,"type":"backgroundColor","color":"#af8"},{"id":"5","tenantId":"","ord":5,"type":"backgroundColor","color":"#8df"},{"id":"6","tenantId":"","ord":6,"type":"backgroundColor","color":"#bbb"},{"id":"7","tenantId":"","ord":7,"type":"backgroundColor","color":"#fff"},{"id":"8","tenantId":"","ord":8,"type":"backgroundColor","color":"rgba(255,255,255,0.5)"},{"id":"9","tenantId":"","ord":9,"type":"color","color":"#875"}]
2 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: Roboto;
3 | }
4 |
5 | @page {
6 | size: A4 landscape;
7 | margin: 0;
8 | }
9 |
10 | @media print {
11 | .no-print {
12 | display: none !important;
13 | }
14 | }
15 |
16 | body {
17 | margin: 0;
18 | overflow-y: hidden;
19 | font-family: 'Roboto', sans-serif;
20 | }
21 |
22 | ul {
23 | list-style-type: none;
24 | padding-left: 0;
25 | margin: 0;
26 | }
27 |
28 | a {
29 | text-decoration: none;
30 | color: inherit;
31 | }
32 |
33 | .elm-overlay {
34 | z-index: 2147483647;
35 | }
36 |
37 | img.emoji {
38 | height: 1.4em;
39 | width: 1.4em;
40 | margin: 0 .05em 0 .1em;
41 | vertical-align: -0.1em;
42 | }
43 |
44 |
45 | /*https://gist.github.com/23maverick23/64b3b587c88697558fac*/
46 |
47 | svg text {
48 | -webkit-user-select: none;
49 | -moz-user-select: none;
50 | -ms-user-select: none;
51 | user-select: none;
52 | }
53 |
54 | svg text::selection {
55 | background: none;
56 | }
57 |
58 | svg tspan {
59 | -webkit-user-select: none;
60 | -moz-user-select: none;
61 | -ms-user-select: none;
62 | user-select: none;
63 | }
64 |
65 | svg tspan::selection {
66 | background: none;
67 | }
68 |
--------------------------------------------------------------------------------
/src/elm/View/SearchInputView.elm:
--------------------------------------------------------------------------------
1 | module View.SearchInputView exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Lazy as Lazy
6 | import Model.I18n as I18n exposing (Language)
7 | import View.Styles as S
8 | import Util.HtmlUtil as HtmlUtil
9 |
10 |
11 | view : Language -> (String -> msg) -> msg -> String -> Html msg
12 | view lang onInputMsg onSubmit query =
13 | HtmlUtil.form_ onSubmit
14 | [ style S.searchBoxContainer ]
15 | [ Lazy.lazy3 textInput lang onInputMsg query
16 | , Lazy.lazy submitButton lang
17 | ]
18 |
19 |
20 | textInput : Language -> (String -> msg) -> String -> Html msg
21 | textInput lang onInputMsg query =
22 | input
23 | [ id "search-box-input"
24 | , placeholder (I18n.searchPlaceHolder lang)
25 | , style S.searchBox
26 | , defaultValue query
27 | , HtmlUtil.onInput onInputMsg
28 | ]
29 | []
30 |
31 |
32 | submitButton : Language -> Html msg
33 | submitButton lang =
34 | input
35 | [ type_ "submit"
36 | , style S.searchBoxSubmit
37 | , value (I18n.search lang)
38 | ]
39 | []
40 |
--------------------------------------------------------------------------------
/src/elm/Util/File.elm:
--------------------------------------------------------------------------------
1 | module Util.File exposing (..)
2 |
3 | import Native.File
4 | import Json.Decode exposing (..)
5 | import Task exposing (Task)
6 |
7 |
8 | type FileList
9 | = FileList Json.Decode.Value
10 |
11 |
12 | type File
13 | = File Json.Decode.Value
14 |
15 |
16 | type Error
17 | = Unexpected String
18 |
19 |
20 | readAsDataURL : File -> Task Error String
21 | readAsDataURL (File file) =
22 | Task.mapError
23 | (always (Unexpected (toString file)))
24 | (Native.File.readAsDataURL file)
25 |
26 |
27 | length : FileList -> Int
28 | length (FileList list) =
29 | Native.File.length list
30 |
31 |
32 | getAt : Int -> FileList -> Maybe File
33 | getAt index fileList =
34 | case fileList of
35 | FileList list ->
36 | if 0 <= index && index < length fileList then
37 | Just (File <| Native.File.getAt index list)
38 | else
39 | Nothing
40 |
41 |
42 | decodeFile : Decoder FileList
43 | decodeFile =
44 | Json.Decode.map FileList (at [ "target", "files" ] (value))
45 |
46 |
47 | getSizeOfImage : String -> ( Int, Int )
48 | getSizeOfImage =
49 | Native.File.getSizeOfImage
50 |
--------------------------------------------------------------------------------
/src/elm/Component/ImageLoader.elm:
--------------------------------------------------------------------------------
1 | module Component.ImageLoader exposing (..)
2 |
3 | import Task
4 | import Html exposing (Html)
5 | import Util.File as File exposing (..)
6 | import Util.HtmlUtil as HtmlUtil
7 | import Model.I18n as I18n exposing (Language)
8 | import View.Styles as S
9 |
10 |
11 | type Msg
12 | = LoadFile FileList
13 |
14 |
15 | type alias Config msg =
16 | { onFileWithDataURL : File -> String -> msg
17 | , onFileLoadFailed : File.Error -> msg
18 | }
19 |
20 |
21 | update : Config msg -> Msg -> Cmd msg
22 | update config message =
23 | case message of
24 | LoadFile fileList ->
25 | File.getAt 0 fileList
26 | |> Maybe.map
27 | (\file ->
28 | readAsDataURL file
29 | |> Task.map (config.onFileWithDataURL file)
30 | |> Task.onError (Task.succeed << config.onFileLoadFailed)
31 | |> Task.perform identity
32 | )
33 | |> Maybe.withDefault Cmd.none
34 |
35 |
36 | view : Language -> Html Msg
37 | view lang =
38 | HtmlUtil.fileLoadButton LoadFile S.imageLoadButton "image/*" (I18n.loadImage lang)
39 |
--------------------------------------------------------------------------------
/server/static/generate-html.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var ejs = require('ejs');
4 | var config = require('../lib/config.js');
5 |
6 | var publicDir = __dirname + '/public';
7 |
8 | var outputFiles = {
9 | index: path.join(publicDir, 'index.html'),
10 | login: path.join(publicDir, 'login.html'),
11 | master: path.join(publicDir, 'master.html')
12 | };
13 |
14 | var templateDir = __dirname + '/template';
15 | var indexHtml = ejs.render(fs.readFileSync(templateDir + '/index.html', 'utf8'), {
16 | apiRoot: config.apiRoot,
17 | accountServiceRoot: config.accountServiceRoot,
18 | title: config.title
19 | });
20 | fs.writeFileSync(outputFiles.index, indexHtml);
21 |
22 | var loginHtml = ejs.render(fs.readFileSync(templateDir + '/login.html', 'utf8'), {
23 | accountServiceRoot: config.accountServiceRoot,
24 | title: config.title
25 | });
26 | fs.writeFileSync(outputFiles.login, loginHtml);
27 |
28 | var masterHtml = ejs.render(fs.readFileSync(templateDir + '/master.html', 'utf8'), {
29 | apiRoot: config.apiRoot,
30 | accountServiceRoot: config.accountServiceRoot,
31 | title: config.title
32 | });
33 | fs.writeFileSync(outputFiles.master, masterHtml);
34 |
35 | return outputFiles;
36 |
--------------------------------------------------------------------------------
/docs/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Office Maker Demo
6 |
7 |
8 |
9 |
14 |
15 |
16 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/elm/View/DialogView.elm:
--------------------------------------------------------------------------------
1 | module View.DialogView exposing (viewWithSize, viewWithMarginParcentage)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Events exposing (..)
6 | import View.CommonStyles as S
7 |
8 |
9 | viewWithSize : msg -> Int -> Int -> Int -> List (Html msg) -> Html msg
10 | viewWithSize clickBackgroundMsg backgroundZIndex width height children =
11 | view clickBackgroundMsg backgroundZIndex (S.dialogWithSize (backgroundZIndex + 1) width height) children
12 |
13 |
14 | viewWithMarginParcentage : msg -> Int -> Int -> Int -> List (Html msg) -> Html msg
15 | viewWithMarginParcentage clickBackgroundMsg backgroundZIndex top left children =
16 | view clickBackgroundMsg backgroundZIndex (S.dialogWithMarginParcentage (backgroundZIndex + 1) top left) children
17 |
18 |
19 | view : msg -> Int -> List ( String, String ) -> List (Html msg) -> Html msg
20 | view clickBackgroundMsg backgroundZIndex dialogStyles children =
21 | div []
22 | [ div
23 | [ style (S.modalBackground backgroundZIndex)
24 | , onClick clickBackgroundMsg
25 | ]
26 | []
27 | , div
28 | [ style dialogStyles ]
29 | children
30 | ]
31 |
--------------------------------------------------------------------------------
/server/lib/mock.js:
--------------------------------------------------------------------------------
1 | var gridSize = 8;
2 | var backgroundColors = [
3 | "#eee", "#edb", "#bbf", "#fbb", "#abe", "#af9", "#9df", "#bbb", "#fff", "rgba(255,255,255,0.5)"
4 | ];
5 | var colors = [
6 | "#875", "#75a", "#c57", "#69a", "#8c5", "#5ab", "#666", "#000"
7 | ];
8 | var prototypes = [{
9 | id: "1",
10 | name: "",
11 | width: gridSize * 7, //70cm
12 | height: gridSize * 12, //120cm
13 | backgroundColor: "#eee",
14 | color: "#000",
15 | fontSize: 20,
16 | shape: 'rectangle'
17 | }, {
18 | id: "2",
19 | name: "",
20 | width: gridSize * 12, //120cm
21 | height: gridSize * 7, //70cm
22 | backgroundColor: "#eee",
23 | color: "#000",
24 | fontSize: 20,
25 | shape: 'rectangle'
26 | }];
27 | var allColors = backgroundColors.map((c, index) => {
28 | var id = index + '';
29 | var ord = index;
30 | return {
31 | id: id,
32 | ord: ord,
33 | type: 'backgroundColor',
34 | color: c
35 | };
36 | }).concat(colors.map((c, index) => {
37 | var id = (backgroundColors.length + index) + '';
38 | var ord = (backgroundColors.length + index);
39 | return {
40 | id: id,
41 | ord: ord,
42 | type: 'color',
43 | color: c
44 | };
45 | }));
46 | module.exports = {
47 | colors: allColors,
48 | prototypes: prototypes
49 | };
50 |
--------------------------------------------------------------------------------
/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "helpful summary of your project, less than 80 characters",
4 | "repository": "https://github.com/user/project.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | "src/elm",
8 | "persistent-cache/src"
9 | ],
10 | "exposed-modules": [],
11 | "native-modules": true,
12 | "dependencies": {
13 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0",
14 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
15 | "elm-lang/dom": "1.0.0 <= v < 2.0.0",
16 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
17 | "elm-lang/http": "1.0.0 <= v < 2.0.0",
18 | "elm-lang/keyboard": "1.0.0 <= v < 2.0.0",
19 | "elm-lang/mouse": "1.0.0 <= v < 2.0.0",
20 | "elm-lang/navigation": "2.0.0 <= v < 3.0.0",
21 | "elm-lang/svg": "2.0.0 <= v < 3.0.0",
22 | "elm-lang/virtual-dom": "2.0.0 <= v < 3.0.0",
23 | "elm-lang/window": "1.0.0 <= v < 2.0.0",
24 | "jinjor/elm-contextmenu": "1.0.2 <= v < 2.0.0",
25 | "jinjor/elm-debounce": "2.0.0 <= v < 3.0.0",
26 | "jinjor/elm-html-parser": "1.0.2 <= v < 2.0.0",
27 | "jinjor/elm-inline-hover": "1.0.0 <= v < 2.0.0",
28 | "jystic/elm-font-awesome": "2.0.0 <= v < 3.0.0"
29 | },
30 | "elm-version": "0.18.0 <= v < 0.19.0"
31 | }
32 |
--------------------------------------------------------------------------------
/src/elm/CoreType.elm:
--------------------------------------------------------------------------------
1 | module CoreType exposing (..)
2 |
3 | import Json.Encode
4 |
5 |
6 | type alias Position =
7 | { x : Int
8 | , y : Int
9 | }
10 |
11 |
12 | type alias PositionFloat =
13 | { x : Float
14 | , y : Float
15 | }
16 |
17 |
18 | type alias Size =
19 | { width : Int
20 | , height : Int
21 | }
22 |
23 |
24 | type alias Id =
25 | String
26 |
27 |
28 | type alias ObjectId =
29 | Id
30 |
31 |
32 | type alias PersonId =
33 | Id
34 |
35 |
36 | type alias FloorId =
37 | Id
38 |
39 |
40 | type alias Json =
41 | Json.Encode.Value
42 |
43 |
44 |
45 | -- DIRECTION
46 |
47 |
48 | type Direction
49 | = Up
50 | | Left
51 | | Right
52 | | Down
53 |
54 |
55 | opposite : Direction -> Direction
56 | opposite direction =
57 | case direction of
58 | Left ->
59 | Right
60 |
61 | Right ->
62 | Left
63 |
64 | Up ->
65 | Down
66 |
67 | Down ->
68 | Up
69 |
70 |
71 | shiftTowards : Direction -> number -> ( number, number )
72 | shiftTowards direction amount =
73 | case direction of
74 | Up ->
75 | ( 0, -amount )
76 |
77 | Down ->
78 | ( 0, amount )
79 |
80 | Right ->
81 | ( amount, 0 )
82 |
83 | Left ->
84 | ( -amount, 0 )
85 |
--------------------------------------------------------------------------------
/src/elm/Native/ClipboardData.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_ClipboardData = function(localRuntime) {
2 | function getHtml(clipboardData) {
3 | return clipboardData.getData('text/html');
4 | }
5 |
6 | function getText(clipboardData) {
7 | return clipboardData.getData('text');
8 | }
9 |
10 | function execCopy(s) {
11 | var textArea = document.createElement("textarea");
12 | textArea.value = s;
13 | document.body.appendChild(textArea);
14 | textArea.select();
15 | var result = document.execCommand("copy");
16 | document.body.removeChild(textArea);
17 | };
18 | return {
19 | execCopy: execCopy,
20 | getHtml: getHtml,
21 | getText: getText
22 | };
23 | }();
24 |
25 |
26 | // var items = clipboardData.items;
27 | // for (var i = 0 ; i < items.length ; i++) {
28 | // var item = items[i];
29 | // console.log(item.type);
30 | // if (item.type.indexOf("image") != -1) {
31 | // var file = item.getAsFile();
32 | // console.log(file);
33 | // }
34 | // }
35 | // console.log(clipboardData.types);
36 | // console.log(clipboardData.files[0]);
37 | // console.log(clipboardData.items[0]);
38 | // console.log(clipboardData.getData('text'));
39 | // console.log(clipboardData.getData('text/html'));
40 | // console.log(clipboardData.getData('text/rtf'));
41 | // console.log(clipboardData.getData('text/plain'));
42 | // console.log(clipboardData.getData('Files'));
43 | // console.log(clipboardData.getData('image/png'));
44 |
--------------------------------------------------------------------------------
/src/elm/View/Common.elm:
--------------------------------------------------------------------------------
1 | module View.Common exposing (card, formControl)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import View.CommonStyles exposing (..)
6 | import Util.StyleUtil exposing (..)
7 |
8 |
9 | card : Bool -> String -> Maybe Int -> Maybe Int -> List (Html msg) -> Html msg
10 | card absolute backgroundColor maxHeight maybeWidth children =
11 | div
12 | [ style (cardStyles absolute backgroundColor maxHeight maybeWidth) ]
13 | children
14 |
15 |
16 | cardStyles : Bool -> String -> Maybe Int -> Maybe Int -> S
17 | cardStyles absolute backgroundColor maybeMaxHeight maybeWidth =
18 | (case maybeMaxHeight of
19 | Just maxHeight ->
20 | [ ( "max-height", px maxHeight )
21 | , ( "overflow-y", "scroll" )
22 | , ( "box-sizing", "border-box" )
23 | ]
24 |
25 | Nothing ->
26 | []
27 | )
28 | ++ [ ( "background-color", backgroundColor )
29 | , ( "width", maybeWidth |> Maybe.map px |> Maybe.withDefault "" )
30 | , ( "position"
31 | , if absolute then
32 | "absolute"
33 | else
34 | ""
35 | )
36 | , ( "z-index"
37 | , if absolute then
38 | "1"
39 | else
40 | ""
41 | )
42 | ]
43 | ++ View.CommonStyles.card
44 |
45 |
46 | formControl : List (Html msg) -> Html msg
47 | formControl children =
48 | div [ style View.CommonStyles.formControl ] children
49 |
--------------------------------------------------------------------------------
/server/sql/2-create-tables.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE map2.floors (
2 | id VARCHAR(36) NOT NULL,
3 | tenantId VARCHAR(64),
4 | version INT NOT NULL,
5 | name VARCHAR(128) NOT NULL,
6 | ord INT NOT NULL,
7 | image VARCHAR(128),
8 | width INT NOT NULL,
9 | height INT NOT NULL,
10 | realWidth INT,
11 | realHeight INT,
12 | public BOOLEAN,
13 | updateBy VARCHAR(128),
14 | updateAt BIGINT,
15 | UNIQUE(id, version)
16 | );
17 |
18 | CREATE TABLE map2.objects (
19 | id VARCHAR(36) NOT NULL,
20 | type VARCHAR(16) NOT NULL,
21 | name VARCHAR(128) NOT NULL,
22 | x INT NOT NULL,
23 | y INT NOT NULL,
24 | width INT NOT NULL,
25 | height INT NOT NULL,
26 | backgroundColor VARCHAR(64) NOT NULL,
27 | color VARCHAR(64) NOT NULL,
28 | fontSize DECIMAL(4,1) NOT NULL,
29 | shape VARCHAR(64) NOT NULL,
30 | modifiedVersion INT(11) NOT NULL,
31 | personId VARCHAR(128),
32 | floorId VARCHAR(36) NOT NULL,
33 | floorVersion INT NOT NULL,
34 | UNIQUE(id, floorId, floorVersion)
35 | );
36 |
37 | CREATE TABLE map2.prototypes (
38 | id VARCHAR(36) NOT NULL PRIMARY KEY,
39 | tenantId VARCHAR(64) NOT NULL,
40 | name VARCHAR(128) NOT NULL,
41 | width INT NOT NULL,
42 | height INT NOT NULL,
43 | backgroundColor VARCHAR(64) NOT NULL,
44 | color VARCHAR(64) NOT NULL,
45 | fontSize DECIMAL(4,1) NOT NULL,
46 | shape VARCHAR(64) NOT NULL
47 | );
48 |
49 | CREATE TABLE map2.colors (
50 | id VARCHAR(36) NOT NULL PRIMARY KEY,
51 | tenantId VARCHAR(64) NOT NULL,
52 | ord INT NOT NULL,
53 | type VARCHAR(16) NOT NULL,
54 | color VARCHAR(64) NOT NULL
55 | );
56 |
--------------------------------------------------------------------------------
/src/elm/Util/ListUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.ListUtil exposing (..)
2 |
3 |
4 | sepBy : Int -> List a -> List (List a)
5 | sepBy count list =
6 | case List.drop count list of
7 | [] ->
8 | [ List.take count list ]
9 |
10 | cont ->
11 | List.take count list :: sepBy count cont
12 |
13 |
14 | findBy : (a -> Bool) -> List a -> Maybe a
15 | findBy f list =
16 | List.head (List.filter f list)
17 |
18 |
19 | zipWithIndex : List a -> List ( a, Int )
20 | zipWithIndex =
21 | zipWithIndexFrom 0
22 |
23 |
24 | zipWithIndexFrom : Int -> List a -> List ( a, Int )
25 | zipWithIndexFrom index list =
26 | case list of
27 | h :: t ->
28 | ( h, index ) :: zipWithIndexFrom (index + 1) t
29 |
30 | _ ->
31 | []
32 |
33 |
34 | getAt : Int -> List a -> Maybe a
35 | getAt index xs =
36 | if index < 0 then
37 | Nothing
38 | else
39 | List.head <| List.drop index xs
40 |
41 |
42 | setAt : Int -> a -> List a -> List a
43 | setAt index value list =
44 | case list of
45 | head :: tail ->
46 | if index == 0 then
47 | value :: tail
48 | else
49 | head :: setAt (index - 1) value tail
50 |
51 | [] ->
52 | list
53 |
54 |
55 | deleteAt : Int -> List a -> List a
56 | deleteAt index list =
57 | case list of
58 | head :: tail ->
59 | if index == 0 then
60 | tail
61 | else
62 | head :: deleteAt (index - 1) tail
63 |
64 | [] ->
65 | list
66 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/GridLayer.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.GridLayer exposing (view)
2 |
3 | import Svg exposing (..)
4 | import Svg.Attributes exposing (..)
5 | import Svg.Lazy exposing (..)
6 | import Model.Scale as Scale exposing (Scale)
7 | import Model.Floor as Floor exposing (Floor)
8 | import Page.Map.Model as Model exposing (Model)
9 |
10 |
11 | view : Model -> Floor -> Svg msg
12 | view model floor =
13 | lazy3 viewHelp model.scale model.gridSize floor
14 |
15 |
16 | viewHelp : Scale -> Int -> Floor -> Svg msg
17 | viewHelp scale gridSize floor =
18 | let
19 | intervalPxOnScreen =
20 | 50 * gridSize
21 |
22 | width =
23 | Floor.width floor
24 |
25 | height =
26 | Floor.height floor
27 |
28 | lefts =
29 | List.map ((*) intervalPxOnScreen) (List.range 1 (width // intervalPxOnScreen))
30 |
31 | tops =
32 | List.map ((*) intervalPxOnScreen) (List.range 1 (height // intervalPxOnScreen))
33 |
34 | vertical =
35 | List.map (lazy2 verticalLine height) lefts
36 |
37 | horizontal =
38 | List.map (lazy2 horizontalLine width) tops
39 | in
40 | g [] (vertical ++ horizontal)
41 |
42 |
43 | verticalLine : Int -> Int -> Svg msg
44 | verticalLine height left =
45 | Svg.path [ strokeDasharray "5,5", stroke "black", d ("M" ++ toString left ++ ",0V" ++ toString height) ] []
46 |
47 |
48 | horizontalLine : Int -> Int -> Svg msg
49 | horizontalLine width top =
50 | Svg.path [ strokeDasharray "5,5", stroke "black", d ("M0," ++ toString top ++ "H" ++ toString width) ] []
51 |
--------------------------------------------------------------------------------
/src/elm/View/MessageBarForMainView.elm:
--------------------------------------------------------------------------------
1 | module View.MessageBarForMainView exposing (view)
2 |
3 | import Http
4 | import Html exposing (..)
5 | import View.MessageBar as MessageBar
6 | import Model.I18n as I18n exposing (Language)
7 | import Model.Errors exposing (GlobalError(..))
8 | import API.API as API
9 |
10 |
11 | view : Language -> GlobalError -> Html msg
12 | view lang e =
13 | case e of
14 | NoError ->
15 | MessageBar.none
16 |
17 | Success message ->
18 | MessageBar.success message
19 |
20 | APIError e ->
21 | MessageBar.error (describeAPIError lang e)
22 |
23 | FileError e ->
24 | MessageBar.error (I18n.unexpectedFileError lang ++ ": " ++ toString e)
25 |
26 | HtmlError e ->
27 | MessageBar.error (I18n.unexpectedHtmlError lang ++ ": " ++ toString e)
28 |
29 | PasteError s ->
30 | MessageBar.error s
31 |
32 |
33 | describeAPIError : Language -> API.Error -> String
34 | describeAPIError lang e =
35 | case e of
36 | Http.BadUrl url ->
37 | I18n.unexpectedBadUrl lang ++ ": " ++ url
38 |
39 | Http.Timeout ->
40 | I18n.timeout lang
41 |
42 | Http.NetworkError ->
43 | I18n.networkErrorDetectedPleaseRefreshAndTryAgain lang
44 |
45 | Http.BadStatus res ->
46 | if res.status.code == 409 then
47 | I18n.conflictSomeoneHasAlreadyChangedPleaseRefreshAndTryAgain lang
48 | else
49 | I18n.unexpectedBadStatus lang ++ ": " ++ toString res.status.code ++ " " ++ res.status.message
50 |
51 | Http.BadPayload str res ->
52 | I18n.unexpectedPayload lang ++ ": " ++ str
53 |
54 |
55 |
56 | --
57 |
--------------------------------------------------------------------------------
/server/lib/sql.js:
--------------------------------------------------------------------------------
1 | var mysql = require('mysql');
2 | var esc = mysql.escape.bind(mysql);
3 |
4 | function select(table, where) {
5 | return `SELECT * FROM ${table}` + (where ? ` ${where}` : '');
6 | }
7 |
8 | function insert(table, keyValues) {
9 | var columns = [];
10 | var values = [];
11 | keyValues.forEach((keyValue) => {
12 | columns.push(keyValue[0]);
13 | values.push(esc(keyValue[1]));
14 | });
15 | var columnsStr = `(${ columns.join(',') })`;
16 | var valuesStr = `VALUES(${ values.join(',') })`;
17 | return `INSERT INTO ${table} ${columnsStr} ${valuesStr}`;
18 | }
19 |
20 | function replace(table, keyValues) {
21 | var columns = [];
22 | var values = [];
23 | keyValues.forEach((keyValue) => {
24 | columns.push(keyValue[0]);
25 | values.push(esc(keyValue[1]));
26 | });
27 | var columnsStr = `(${ columns.join(',') })`;
28 | var valuesStr = `VALUES(${ values.join(',') })`;
29 | return `REPLACE INTO ${table} ${columnsStr} ${valuesStr}`;
30 | }
31 |
32 | function update(table, keyValues, where) {
33 | var sets = keyValues.map((keyValue) => {
34 | return `${ keyValue[0] }=${ esc(keyValue[1]) }`;
35 | });
36 | var str = `SET ${ sets.join(',') }`;
37 | return `UPDATE ${ table } ${ str } ${ where }`;
38 | }
39 |
40 | function where(key, value) {
41 | return `WHERE ${key}=${esc(value)}`;
42 | }
43 |
44 | function whereList(keyValues) {
45 | return `WHERE ` + keyValues.map((keyValue) => {
46 | return `${keyValue[0]}=${esc(keyValue[1])}`;
47 | }).join(' AND ');
48 | }
49 |
50 | function _delete(table, where) {
51 | return `DELETE FROM ${ table }` + (where ? ` ${where}` : '');
52 | }
53 | module.exports = {
54 | select: select,
55 | insert: insert,
56 | replace: replace,
57 | update: update,
58 | delete: _delete,
59 | where: where,
60 | whereList: whereList
61 | };
62 |
--------------------------------------------------------------------------------
/src/elm/Model/SaveRequest.elm:
--------------------------------------------------------------------------------
1 | module Model.SaveRequest exposing (..)
2 |
3 | import Model.Floor exposing (Floor)
4 | import Model.EditingFloor as EditingFloor exposing (EditingFloor)
5 | import Model.ObjectsChange as ObjectsChange exposing (ObjectsChange)
6 | import CoreType exposing (..)
7 |
8 |
9 | type SaveRequest
10 | = SaveFloor Floor
11 | | PublishFloor FloorId
12 | | SaveObjects ObjectsChange
13 |
14 |
15 | type alias ReducedSaveRequest =
16 | { floor : Maybe Floor
17 | , publish : Maybe FloorId
18 | , objects : ObjectsChange
19 | }
20 |
21 |
22 | emptyReducedSaveRequest : ReducedSaveRequest
23 | emptyReducedSaveRequest =
24 | { floor = Nothing
25 | , publish = Nothing
26 | , objects = ObjectsChange.empty
27 | }
28 |
29 |
30 | reduceRequest : Maybe EditingFloor -> List SaveRequest -> ReducedSaveRequest
31 | reduceRequest maybeEditingFloor list =
32 | case maybeEditingFloor of
33 | Just efloor ->
34 | List.foldr (reduceRequestHelp efloor) emptyReducedSaveRequest list
35 |
36 | Nothing ->
37 | emptyReducedSaveRequest
38 |
39 |
40 | reduceRequestHelp : EditingFloor -> SaveRequest -> ReducedSaveRequest -> ReducedSaveRequest
41 | reduceRequestHelp efloor req reducedSaveRequest =
42 | case req of
43 | SaveFloor floor ->
44 | { reducedSaveRequest
45 | | floor = Just floor
46 | }
47 |
48 | PublishFloor floorId ->
49 | { reducedSaveRequest
50 | | publish = Just floorId
51 | }
52 |
53 | SaveObjects objectsChange ->
54 | { reducedSaveRequest
55 | | objects =
56 | ObjectsChange.merge
57 | (EditingFloor.present efloor).objects
58 | objectsChange
59 | reducedSaveRequest.objects
60 | }
61 |
--------------------------------------------------------------------------------
/persistent-cache/src/LocalStorage.elm:
--------------------------------------------------------------------------------
1 | module LocalStorage
2 | exposing
3 | ( get
4 | , set
5 | , remove
6 | , clear
7 | , keys
8 | , Error(..)
9 | )
10 |
11 | {-| Low-level bindings to the [localStorage] API.
12 | -}
13 |
14 | import Native.LocalStorage
15 | import Task exposing (Task)
16 |
17 |
18 | {-| These low-level operations can fail in a few ways:
19 |
20 | - `QuotaExceeded` means you exceeded your 5mb and need to `clear` or `remove`
21 | some information to make more space.
22 | - `Disabled` means the user turned off local storage. It is rare, but it can
23 | happen.
24 |
25 | -}
26 | type Error
27 | = QuotaExceeded
28 | | Disabled
29 |
30 |
31 | {-| Get the value at a particular key.
32 |
33 | get "age"
34 |
35 | -}
36 | get : String -> Task Error (Maybe String)
37 | get =
38 | Native.LocalStorage.get
39 |
40 |
41 | {-| Set a key to a particular value. If the key does not exist, it is added.
42 | If the key already exists, we overwrite the old data.
43 |
44 | set "age" "42"
45 |
46 | Most browsers cap you at 5MB of space, so this can trigger a `QuotaExceeded`
47 | error if you are adding enough data to cross that threshold.
48 |
49 | -}
50 | set : String -> String -> Task Error ()
51 | set =
52 | Native.LocalStorage.set
53 |
54 |
55 | {-| Remove a particular key and its corresponding value.
56 |
57 | remove "age"
58 |
59 | -}
60 | remove : String -> Task Error ()
61 | remove =
62 | Native.LocalStorage.remove
63 |
64 |
65 | {-| Remove everything in local storage.
66 | -}
67 | clear : Task Error ()
68 | clear =
69 | Native.LocalStorage.clear
70 |
71 |
72 | {-| Get all the keys currently stored. So if you `set` two entries named
73 | `"draft"` and `"title"`, running the `keys` task will produce
74 | `["draft", "title"]`. If you have not `set` any entries yet, you will get `[]`.
75 | -}
76 | keys : Task Error (List String)
77 | keys =
78 | Native.LocalStorage.keys
79 |
--------------------------------------------------------------------------------
/src/elm/Model/ColorPalette.elm:
--------------------------------------------------------------------------------
1 | module Model.ColorPalette exposing (..)
2 |
3 | import Util.ListUtil as ListUtil
4 |
5 |
6 | type alias ColorPalette =
7 | { backgroundColors : List String
8 | , textColors : List String
9 | }
10 |
11 |
12 | empty : ColorPalette
13 | empty =
14 | { backgroundColors = []
15 | , textColors = []
16 | }
17 |
18 |
19 | setTextColorAt : Int -> String -> ColorPalette -> ColorPalette
20 | setTextColorAt index color colorPalette =
21 | { colorPalette
22 | | textColors = ListUtil.setAt index color colorPalette.textColors
23 | }
24 |
25 |
26 | setBackgroundColorAt : Int -> String -> ColorPalette -> ColorPalette
27 | setBackgroundColorAt index color colorPalette =
28 | { colorPalette
29 | | backgroundColors = ListUtil.setAt index color colorPalette.backgroundColors
30 | }
31 |
32 |
33 | addTextColorToLast : String -> ColorPalette -> ColorPalette
34 | addTextColorToLast color colorPalette =
35 | { colorPalette
36 | | textColors =
37 | colorPalette.textColors
38 | |> List.reverse
39 | |> ((::) color)
40 | |> List.reverse
41 | }
42 |
43 |
44 | addBackgroundColorToLast : String -> ColorPalette -> ColorPalette
45 | addBackgroundColorToLast color colorPalette =
46 | { colorPalette
47 | | backgroundColors =
48 | colorPalette.backgroundColors
49 | |> List.reverse
50 | |> ((::) color)
51 | |> List.reverse
52 | }
53 |
54 |
55 | deleteTextColorAt : Int -> ColorPalette -> ColorPalette
56 | deleteTextColorAt index colorPalette =
57 | { colorPalette
58 | | textColors = ListUtil.deleteAt index colorPalette.textColors
59 | }
60 |
61 |
62 | deleteBackgroundColorAt : Int -> ColorPalette -> ColorPalette
63 | deleteBackgroundColorAt index colorPalette =
64 | { colorPalette
65 | | backgroundColors = ListUtil.deleteAt index colorPalette.backgroundColors
66 | }
67 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/FloorUpdateInfoView.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.FloorUpdateInfoView exposing (view)
2 |
3 | import Dict
4 | import Date exposing (Date)
5 | import Html exposing (..)
6 | import Html.Attributes exposing (..)
7 | import Html.Lazy as Lazy
8 | import View.Styles as S
9 | import Model.DateFormatter as DateFormatter
10 | import Model.I18n as I18n exposing (Language)
11 | import Model.EditingFloor as EditingFloor
12 | import Model.Mode as Mode
13 | import Page.Map.Model exposing (Model)
14 |
15 |
16 | view : Model -> Html msg
17 | view model =
18 | model.floor
19 | |> Maybe.map EditingFloor.present
20 | |> Maybe.andThen (\floor -> floor.update)
21 | |> Maybe.map
22 | (\{ by, at } ->
23 | let
24 | name =
25 | Dict.get by model.personInfo
26 | |> Maybe.map .name
27 | |> Maybe.withDefault by
28 | in
29 | viewHelp model.lang model.visitDate name at (Mode.isPrintMode model.mode)
30 | )
31 | |> Maybe.withDefault (text "")
32 |
33 |
34 | viewHelp : Language -> Date -> String -> Date -> Bool -> Html msg
35 | viewHelp lang visitDate by at printMode =
36 | Lazy.lazy2
37 | viewHelpHelp
38 | printMode
39 | (I18n.lastUpdateByAt lang by (formatDate lang printMode visitDate at))
40 |
41 |
42 | formatDate : Language -> Bool -> Date -> Date -> String
43 | formatDate lang printMode visitDate at =
44 | if printMode then
45 | DateFormatter.formatDate lang at
46 | else
47 | DateFormatter.formatDateOrTime lang visitDate at
48 |
49 |
50 | viewHelpHelp : Bool -> String -> Html msg
51 | viewHelpHelp printMode string =
52 | div
53 | [ style
54 | (if printMode then
55 | S.floorPropertyLastUpdateForPrint
56 | else
57 | S.floorPropertyLastUpdate
58 | )
59 | ]
60 | [ text string ]
61 |
--------------------------------------------------------------------------------
/watch.js:
--------------------------------------------------------------------------------
1 | const cp = require('child_process');
2 | const watch = require('watch');
3 | const path = require('path');
4 | const slash = require('slash');
5 | const minimatch = require('minimatch');
6 |
7 | var runServer = !process.argv.filter(a => {
8 | return a == '--no-server'
9 | }).length;
10 | var debugMode = process.argv.filter(a => {
11 | return a == '--debug'
12 | }).length;
13 | var server = null;
14 |
15 | var queued = {
16 | build: false,
17 | server: false
18 | };
19 |
20 | function taskBuild(cb) {
21 | if (queued.build) {
22 | queued.build = false;
23 | // console.log('build start\n');
24 | var args = ['build.sh'];
25 | if (debugMode) {
26 | args.push('--debug');
27 | }
28 | var sh = cp.spawn('sh', args, {
29 | stdio: 'inherit'
30 | });
31 | sh.on('close', cb);
32 | } else {
33 | cb();
34 | }
35 | }
36 |
37 | function taskServer(cb) {
38 | if (runServer && queued.server) {
39 | queued.server = false;
40 | server && server.kill();
41 | server = cp.spawn('node', ['server/server'], {
42 | stdio: 'inherit'
43 | });
44 | cb();
45 | } else {
46 | cb();
47 | }
48 | }
49 |
50 | function run() {
51 | taskBuild(() => {
52 | taskServer(() => {
53 | setTimeout(run, 300);
54 | });
55 | });
56 | }
57 |
58 | function schedule(type, stat) {
59 | queued[type] = true;
60 | }
61 | watch.createMonitor('src', (monitor) => {
62 | monitor.on("created", schedule.bind(null, 'build'));
63 | monitor.on("changed", schedule.bind(null, 'build'));
64 | monitor.on("removed", schedule.bind(null, 'build'));
65 | });
66 |
67 | watch.createMonitor('server', {
68 | filter: function(file) {
69 | return !file.includes('public') && !file.includes('node_modules');
70 | }
71 | }, (monitor) => {
72 | monitor.on("created", schedule.bind(null, 'server'));
73 | monitor.on("changed", schedule.bind(null, 'server'));
74 | monitor.on("removed", schedule.bind(null, 'server'));
75 | });
76 |
77 | schedule('build');
78 | schedule('server');
79 | run();
80 |
--------------------------------------------------------------------------------
/src/elm/Util/UUID.elm:
--------------------------------------------------------------------------------
1 | module Util.UUID exposing (Seed, init, step)
2 |
3 | -- TODO maybe this implementation is incorrect. Use library instead.
4 |
5 | import Random
6 |
7 |
8 | type Seed
9 | = Seed Random.Seed Random.Seed
10 |
11 |
12 | r1 : Random.Generator String
13 | r1 =
14 | Random.map toHex <| Random.int 0 15
15 |
16 |
17 | r2 : Random.Generator String
18 | r2 =
19 | Random.map toHex <| Random.int 8 11
20 |
21 |
22 | toHex : Int -> String
23 | toHex i =
24 | if i == 0 then
25 | "0"
26 | else if i == 1 then
27 | "1"
28 | else if i == 2 then
29 | "2"
30 | else if i == 3 then
31 | "3"
32 | else if i == 4 then
33 | "4"
34 | else if i == 5 then
35 | "5"
36 | else if i == 6 then
37 | "6"
38 | else if i == 7 then
39 | "7"
40 | else if i == 8 then
41 | "8"
42 | else if i == 9 then
43 | "9"
44 | else if i == 10 then
45 | "a"
46 | else if i == 11 then
47 | "b"
48 | else if i == 12 then
49 | "c"
50 | else if i == 13 then
51 | "d"
52 | else if i == 14 then
53 | "e"
54 | else if i == 15 then
55 | "f"
56 | else
57 | Debug.crash ""
58 |
59 |
60 | f : Char -> ( String, Random.Seed, Random.Seed ) -> ( String, Random.Seed, Random.Seed )
61 | f c ( s, s1, s2 ) =
62 | if c == 'x' then
63 | let
64 | ( c_, s1_ ) =
65 | Random.step r1 s1
66 | in
67 | ( s ++ c_, s1_, s2 )
68 | else if c == 'y' then
69 | let
70 | ( c_, s2_ ) =
71 | Random.step r2 s2
72 | in
73 | ( s ++ c_, s1, s2_ )
74 | else
75 | ( s ++ String.fromChar c, s1, s2 )
76 |
77 |
78 | init : Int -> Int -> Seed
79 | init i1 i2 =
80 | Seed (Random.initialSeed i1) (Random.initialSeed i2)
81 |
82 |
83 | step : Seed -> ( String, Seed )
84 | step (Seed s1 s2) =
85 | let
86 | ( s, s1_, s2_ ) =
87 | List.foldl f ( "", s1, s2 ) (String.toList "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
88 | in
89 | ( s, Seed s1_ s2_ )
90 |
--------------------------------------------------------------------------------
/src/elm/View/PrototypePreviewView.elm:
--------------------------------------------------------------------------------
1 | module View.PrototypePreviewView exposing (view, singleView, emptyView)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Svg exposing (Svg)
6 | import Svg.Attributes
7 | import View.Styles as S
8 | import View.ObjectView as ObjectView
9 | import Model.Scale as Scale exposing (Scale)
10 | import Model.Prototype exposing (Prototype)
11 | import CoreType exposing (..)
12 |
13 |
14 | view : Size -> Int -> List Prototype -> Html msg
15 | view containerSize selectedIndex prototypes =
16 | let
17 | inner =
18 | Svg.svg
19 | [ Svg.Attributes.width (toString <| containerSize.width * 4)
20 | , Svg.Attributes.height (toString containerSize.height)
21 | , style (S.prototypePreviewViewInner containerSize selectedIndex)
22 | ]
23 | (List.indexedMap (eachView containerSize selectedIndex) prototypes)
24 | in
25 | div [ style (S.prototypePreviewView containerSize.width containerSize.height) ] [ inner ]
26 |
27 |
28 | singleView : Size -> Prototype -> Html msg
29 | singleView containerSize prototype =
30 | view containerSize 0 [ prototype ]
31 |
32 |
33 | emptyView : Size -> Html msg
34 | emptyView containerSize =
35 | view containerSize 0 []
36 |
37 |
38 | eachView : Size -> Int -> Int -> Prototype -> Html msg
39 | eachView containerSize selectedIndex index prototype =
40 | let
41 | selected =
42 | selectedIndex == index
43 |
44 | left =
45 | containerSize.width // 2 - prototype.width // 2 + index * containerSize.width
46 |
47 | top =
48 | containerSize.height // 2 - prototype.height // 2
49 | in
50 | ObjectView.viewDesk
51 | ObjectView.noEvents
52 | False
53 | (Position left top)
54 | (Size prototype.width prototype.height)
55 | prototype.backgroundColor
56 | prototype.name
57 | --name
58 | prototype.fontSize
59 | False
60 | -- selected
61 | False
62 | -- alpha
63 | (Scale.init 0)
64 | False
65 |
66 |
67 |
68 | -- personMatched
69 |
--------------------------------------------------------------------------------
/persistent-cache/src/Native/LocalStorage.js:
--------------------------------------------------------------------------------
1 | var _user$project$Native_LocalStorage = function() {
2 |
3 | if (!localStorage || !localStorage.getItem || !localStorage.setItem) {
4 | function disabled() {
5 | return _elm_lang$core$Native_Scheduler.fail({
6 | ctor: 'Disabled'
7 | });
8 | }
9 |
10 | return {
11 | get: disabled,
12 | set: F2(disabled),
13 | remove: disabled,
14 | clear: disabled(),
15 | keys: disabled()
16 | };
17 | }
18 |
19 | function get(key) {
20 | return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
21 | var value = localStorage.getItem(key);
22 | callback(_elm_lang$core$Native_Scheduler.succeed(
23 | value === null ?
24 | _elm_lang$core$Maybe$Nothing :
25 | _elm_lang$core$Maybe$Just(value)
26 | ));
27 | });
28 | }
29 |
30 | function set(key, value) {
31 | return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
32 | try {
33 | localStorage.setItem(key, value);
34 | return callback(_elm_lang$core$Native_Scheduler.succeed(_elm_lang$core$Native_Utils.Tuple0));
35 | } catch (e) {
36 | return callback(_elm_lang$core$Native_Scheduler.fail({
37 | ctor: 'QuotaExceeded'
38 | }));
39 | }
40 | });
41 | }
42 |
43 | function remove(key) {
44 | return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
45 | localStorage.removeItem(key);
46 | callback(_elm_lang$core$Native_Scheduler.succeed(_elm_lang$core$Native_Utils.Tuple0));
47 | });
48 | }
49 |
50 | var clear = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
51 | localStorage.clear();
52 | callback(_elm_lang$core$Native_Scheduler.succeed(_elm_lang$core$Native_Utils.Tuple0));
53 | });
54 |
55 | var keys = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
56 | var keyList = _elm_lang$core$Native_List.Nil;
57 | for (var i = localStorage.length; i--;) {
58 | keyList = _elm_lang$core$Native_List.Cons(localStorage.key(i), keyList);
59 | }
60 | callback(_elm_lang$core$Native_Scheduler.succeed(keyList));
61 | });
62 |
63 | return {
64 | get: get,
65 | set: F2(set),
66 | remove: remove,
67 | clear: clear,
68 | keys: keys
69 | };
70 |
71 | }();
72 |
--------------------------------------------------------------------------------
/src/elm/Component/Dialog.elm:
--------------------------------------------------------------------------------
1 | module Component.Dialog exposing (..)
2 |
3 | import Task
4 | import Html exposing (..)
5 | import Html.Attributes exposing (..)
6 | import Html.Events exposing (..)
7 | import View.DialogView as DialogView
8 | import View.CommonStyles as S
9 |
10 |
11 | type alias Config msg =
12 | { strategy : Strategy msg
13 | , transform : Msg msg -> msg
14 | }
15 |
16 |
17 | type Strategy msg
18 | = ConfirmOrClose ( String, msg ) ( String, msg )
19 |
20 |
21 | type alias Dialog =
22 | Bool
23 |
24 |
25 | type Msg msg
26 | = Open
27 | | Confirm msg
28 | | Close msg
29 |
30 |
31 | init : Dialog
32 | init =
33 | False
34 |
35 |
36 | update : Msg msg -> Dialog -> ( Dialog, Cmd msg )
37 | update msg model =
38 | case msg of
39 | Open ->
40 | True ! []
41 |
42 | Close msg ->
43 | False ! [ Task.perform identity <| Task.succeed msg ]
44 |
45 | Confirm msg ->
46 | False ! [ Task.perform identity <| Task.succeed msg ]
47 |
48 |
49 | open : (Msg msg -> msg) -> Cmd msg
50 | open f =
51 | Task.perform identity (Task.succeed (f Open))
52 |
53 |
54 | view : Config msg -> String -> Dialog -> Html msg
55 | view config content opened =
56 | if opened then
57 | let
58 | footer =
59 | case config.strategy of
60 | ConfirmOrClose ( confirmText, confirmMsg ) ( cancelText, cancelMsg ) ->
61 | Html.map config.transform <|
62 | div [ style S.dialogFooter ]
63 | [ button [ style S.defaultButton, onClick (Close cancelMsg) ] [ text cancelText ]
64 | , button [ style S.primaryButton, onClick (Confirm confirmMsg) ] [ text confirmText ]
65 | ]
66 |
67 | clickBackgroundMsg =
68 | config.transform <|
69 | case config.strategy of
70 | ConfirmOrClose _ ( cancelText, cancelMsg ) ->
71 | Close cancelMsg
72 | in
73 | DialogView.viewWithSize clickBackgroundMsg
74 | 100000
75 | 300
76 | 150
77 | [ div [] [ text content ]
78 | , footer
79 | ]
80 | else
81 | text ""
82 |
83 |
84 |
85 | --
86 |
--------------------------------------------------------------------------------
/src/elm/Component/FloorDeleter.elm:
--------------------------------------------------------------------------------
1 | module Component.FloorDeleter exposing (..)
2 |
3 | import Dict
4 | import Html exposing (..)
5 | import Html.Attributes exposing (..)
6 | import InlineHover exposing (hover)
7 | import Util.HtmlUtil as HtmlUtil
8 | import Model.I18n as I18n exposing (Language)
9 | import Model.User as User exposing (User)
10 | import Model.Floor exposing (Floor)
11 | import Component.Dialog as Dialog exposing (Dialog)
12 | import View.Styles as S
13 |
14 |
15 | type Msg
16 | = SelectDeleteFloor
17 | | DialogMsg (Dialog.Msg Msg)
18 | | DeleteFloor Floor Bool
19 |
20 |
21 | type alias Config msg =
22 | { onDeleteFloor : Floor -> Cmd msg
23 | }
24 |
25 |
26 | type alias FloorDeleter =
27 | { dialog : Dialog
28 | }
29 |
30 |
31 | init : FloorDeleter
32 | init =
33 | { dialog = Dialog.init
34 | }
35 |
36 |
37 | update : (Msg -> msg) -> Config msg -> Msg -> FloorDeleter -> ( FloorDeleter, Cmd msg )
38 | update transform config message model =
39 | case message of
40 | SelectDeleteFloor ->
41 | ( model, Dialog.open DialogMsg |> Cmd.map transform )
42 |
43 | DialogMsg msg ->
44 | let
45 | ( dialog, cmd ) =
46 | Dialog.update msg model.dialog
47 | in
48 | ( { model
49 | | dialog = dialog
50 | }
51 | , cmd |> Cmd.map transform
52 | )
53 |
54 | DeleteFloor floor ok ->
55 | ( model
56 | , if ok then
57 | config.onDeleteFloor floor
58 | else
59 | Cmd.none
60 | )
61 |
62 |
63 | button : Language -> User -> Floor -> Html Msg
64 | button lang user floor =
65 | if User.isAdmin user && Dict.isEmpty floor.objects then
66 | hover S.deleteFloorButtonHover
67 | Html.button
68 | [ HtmlUtil.onClick_ SelectDeleteFloor
69 | , style S.deleteFloorButton
70 | ]
71 | [ text (I18n.deleteFloor lang)
72 | ]
73 | else
74 | text ""
75 |
76 |
77 | dialog : Floor -> FloorDeleter -> Html Msg
78 | dialog floor model =
79 | Dialog.view
80 | { strategy =
81 | Dialog.ConfirmOrClose
82 | ( "delete", DeleteFloor floor True )
83 | ( "cancel", DeleteFloor floor False )
84 | , transform = DialogMsg
85 | }
86 | "delete this floor?"
87 | model.dialog
88 |
--------------------------------------------------------------------------------
/src/elm/Model/Scale.elm:
--------------------------------------------------------------------------------
1 | module Model.Scale exposing (..)
2 |
3 | import CoreType exposing (..)
4 |
5 |
6 | -- CONFIG
7 |
8 |
9 | minScale : Int
10 | minScale =
11 | 0
12 |
13 |
14 | maxScale : Int
15 | maxScale =
16 | 8
17 |
18 |
19 | defaultScale : Int
20 | defaultScale =
21 | 4
22 |
23 |
24 | step : Float
25 | step =
26 | 1.414
27 |
28 |
29 |
30 | -- TEA
31 |
32 |
33 | type alias Scale =
34 | { scaleDown : Int
35 | }
36 |
37 |
38 | default : Scale
39 | default =
40 | init defaultScale
41 |
42 |
43 | init : Int -> Scale
44 | init scaleDown =
45 | Scale scaleDown
46 |
47 |
48 | type Msg
49 | = ScaleUp
50 | | ScaleDown
51 |
52 |
53 | update : Msg -> Scale -> Scale
54 | update msg scale =
55 | case msg of
56 | ScaleUp ->
57 | { scale | scaleDown = max minScale (scale.scaleDown - 1) }
58 |
59 | ScaleDown ->
60 | { scale | scaleDown = min maxScale (scale.scaleDown + 1) }
61 |
62 |
63 |
64 | -- FUNCTIONS
65 |
66 |
67 | screenToImageForPosition : Scale -> Position -> Position
68 | screenToImageForPosition scale screenPosition =
69 | Position
70 | (screenToImage scale screenPosition.x)
71 | (screenToImage scale screenPosition.y)
72 |
73 |
74 | imageToScreenForPosition : Scale -> Position -> Position
75 | imageToScreenForPosition scale imagePosition =
76 | Position
77 | (imageToScreen scale imagePosition.x)
78 | (imageToScreen scale imagePosition.y)
79 |
80 |
81 | imageToScreenForSize : Scale -> Size -> Size
82 | imageToScreenForSize scale { width, height } =
83 | Size (imageToScreen scale width) (imageToScreen scale height)
84 |
85 |
86 | screenToImageForSize : Scale -> Size -> Size
87 | screenToImageForSize scale { width, height } =
88 | Size (screenToImage scale width) (screenToImage scale height)
89 |
90 |
91 | screenToImage : Scale -> Int -> Int
92 | screenToImage scale imageLength =
93 | round (toFloat imageLength * step ^ toFloat scale.scaleDown)
94 |
95 |
96 | imageToScreen : Scale -> Int -> Int
97 | imageToScreen scale screenLength =
98 | round (toFloat screenLength / step ^ toFloat scale.scaleDown)
99 |
100 |
101 | imageToScreenRatio : Scale -> Float
102 | imageToScreenRatio scale =
103 | 1.0 / step ^ (toFloat scale.scaleDown)
104 |
105 |
106 | ratio : Scale -> Scale -> Float
107 | ratio old new =
108 | (step ^ toFloat old.scaleDown) / (step ^ toFloat new.scaleDown)
109 |
--------------------------------------------------------------------------------
/src/elm/Util/UndoList.elm:
--------------------------------------------------------------------------------
1 | module Util.UndoList exposing (UndoList, init, undo, undoReplace, redo, redoReplace, new)
2 |
3 |
4 | type alias UndoList a =
5 | { past : List a
6 | , present : a
7 | , future : List a
8 | }
9 |
10 |
11 | init : a -> UndoList a
12 | init data =
13 | { past = []
14 | , present = data
15 | , future = []
16 | }
17 |
18 |
19 | undo : UndoList a -> UndoList a
20 | undo undoList =
21 | case undoList.past of
22 | x :: xs ->
23 | { undoList
24 | | past = xs
25 | , present = x
26 | , future = undoList.present :: undoList.future
27 | }
28 |
29 | _ ->
30 | undoList
31 |
32 |
33 | undoReplace : b -> (a -> a -> ( a, b )) -> UndoList a -> ( UndoList a, b )
34 | undoReplace default f undoList =
35 | case undoList.past of
36 | x :: xs ->
37 | let
38 | ( newPresent, b ) =
39 | f x undoList.present
40 | in
41 | ( { undoList
42 | | past = xs
43 | , present = newPresent
44 | , future = undoList.present :: undoList.future
45 | }
46 | , b
47 | )
48 |
49 | _ ->
50 | ( undoList, default )
51 |
52 |
53 | redo : UndoList a -> UndoList a
54 | redo undoList =
55 | case undoList.future of
56 | x :: xs ->
57 | { undoList
58 | | past = undoList.present :: undoList.past
59 | , present = x
60 | , future = xs
61 | }
62 |
63 | _ ->
64 | undoList
65 |
66 |
67 | redoReplace : b -> (a -> a -> ( a, b )) -> UndoList a -> ( UndoList a, b )
68 | redoReplace default f undoList =
69 | case undoList.future of
70 | x :: xs ->
71 | let
72 | ( newPresent, b ) =
73 | f x undoList.present
74 | in
75 | ( { undoList
76 | | past = undoList.present :: undoList.past
77 | , present = newPresent
78 | , future = xs
79 | }
80 | , b
81 | )
82 |
83 | _ ->
84 | ( undoList, default )
85 |
86 |
87 | new : a -> UndoList a -> UndoList a
88 | new a undoList =
89 | { undoList
90 | | past = undoList.present :: undoList.past
91 | , present = a
92 | , future = []
93 | }
94 |
95 |
96 |
97 | --
98 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/PrintGuide.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.PrintGuide exposing (view)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Events exposing (..)
6 | import Html.Lazy as Lazy exposing (..)
7 | import Model.I18n as I18n exposing (Language)
8 | import View.Styles as Styles
9 | import Util.StyleUtil exposing (..)
10 | import Page.Map.Msg exposing (Msg(..))
11 |
12 |
13 | view : Language -> Bool -> Html Msg
14 | view lang isPrintMode =
15 | if isPrintMode then
16 | div
17 | [ style containerStyle
18 | , class "no-print"
19 | ]
20 | [ div [ style (itemStyle 2245 1587) ] [ text "A2" ]
21 | , div [ style (itemStyle 1587 1122) ] [ text "A3" ]
22 | , div [ style (itemStyle 1122 793) ] [ text "A4", lazy button lang ]
23 | ]
24 | else
25 | text ""
26 |
27 |
28 | button : Language -> Html Msg
29 | button lang =
30 | div
31 | [ style buttonStyle
32 | , onClick Print
33 | ]
34 | [ text (I18n.print lang) ]
35 |
36 |
37 | color : String
38 | color =
39 | "rgb(200, 150, 220)"
40 |
41 |
42 | containerStyle : List ( String, String )
43 | containerStyle =
44 | [ ( "position", "fixed" )
45 | , ( "z-index", Styles.zPrintGuide )
46 | , ( "top", "0" )
47 | , ( "left", "0" )
48 | , ( "pointer-events", "none" )
49 | ]
50 |
51 |
52 | itemStyle : Int -> Int -> List ( String, String )
53 | itemStyle width height =
54 | [ ( "position", "fixed" )
55 | , ( "top", "0" )
56 | , ( "left", "0" )
57 | , ( "width", px width )
58 | , ( "height", px height )
59 | , ( "border", "dashed 5px " ++ color )
60 | , ( "font-size", "x-large" )
61 | , ( "font-weight", "bold" )
62 | , ( "color", color )
63 | , ( "text-align", "right" )
64 | , ( "padding-right", "3px" )
65 | ]
66 |
67 |
68 | buttonStyle : List ( String, String )
69 | buttonStyle =
70 | [ ( "border", "2px solid " ++ color )
71 | , ( "color", "white" )
72 | , ( "margin", "auto" )
73 | , ( "position", "absolute" )
74 | , ( "top", "0" )
75 | , ( "bottom", "0" )
76 | , ( "left", "0" )
77 | , ( "right", "0" )
78 | , ( "width", "300px" )
79 | , ( "height", "150px" )
80 | , ( "line-height", "150px" )
81 | , ( "text-align", "center" )
82 | , ( "font-size", "3em" )
83 | , ( "font-weight", "normal" )
84 | , ( "cursor", "pointer" )
85 | , ( "background-color", color )
86 | , ( "opacity", "0.4" )
87 | , ( "pointer-events", "all" )
88 | ]
89 |
--------------------------------------------------------------------------------
/server/lib/account-service.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var log = require('./log.js');
3 |
4 | function send(token, method, url, data) {
5 | return new Promise((resolve, reject) => {
6 | var options = {
7 | method: method,
8 | url: url,
9 | headers: {
10 | 'Authorization': 'JWT ' + token
11 | },
12 | body: data,
13 | json: true
14 | };
15 | request(options, function(e, response, body) {
16 | if (e || response.statusCode >= 400) {
17 | log.system.error(response.statusCode, 'account service: failed ' + method + ' ' + url);
18 | body && log.system.error(body.message);
19 | if (response && response.statusCode === 401) {
20 | reject(401);
21 | } else {
22 | reject(body ? body.message : e || response.statusCode);
23 | }
24 | } else {
25 | log.system.debug(response.statusCode, 'account service: success ' + method + ' ' + url);
26 | resolve(body);
27 | }
28 | });
29 | });
30 | }
31 |
32 | function get(token, url) {
33 | return send(token, 'GET', url);
34 | }
35 |
36 | function post(token, url, data) {
37 | return send(token, 'POST', url, data);
38 | }
39 |
40 | function login(root, userId, password) {
41 | return post('', root + '/1/authentication', {
42 | userId: userId,
43 | password: password
44 | }).then(obj => {
45 | return obj.accessToken;
46 | });
47 | }
48 |
49 | function addUser(root, token, user) {
50 | user.userId = user.id;
51 | user.password = user.pass;
52 | user.role = user.role.toUpperCase();
53 | return post(token, root + '/1/users', user);
54 | }
55 |
56 | function getAllAdmins(root, token, exclusiveStartKey) {
57 | var url = root + '/1/users' +
58 | (exclusiveStartKey ? '?exclusiveStartKey=' + exclusiveStartKey : '')
59 | return get(token, url).then((data) => {
60 | var users = data.users.map(user => {
61 | toMapUser(user);
62 | return user;
63 | }).filter(user => {
64 | return user.role === 'admin';
65 | });
66 | if (data.lastEvaluatedKey) {
67 | return getAllAdmins(root, token, data.lastEvaluatedKey).then((users2) => {
68 | return Promise.resolve(users.concat(users2));
69 | });
70 | } else {
71 | return Promise.resolve(users);
72 | }
73 | });
74 | }
75 |
76 | function toMapUser(user) {
77 | user.id = user.id || user.userId;
78 | user.role = user.role.toLowerCase();
79 | user.tenantId = '';
80 | }
81 |
82 | module.exports = {
83 | addUser: addUser,
84 | login: login,
85 | getAllAdmins: getAllAdmins,
86 | toMapUser: toMapUser
87 | };
88 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/URL.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.URL exposing (..)
2 |
3 | import Dict
4 | import Navigation exposing (Location)
5 | import Util.UrlParser as UrlParser
6 |
7 |
8 | type alias URL =
9 | { floorId : Maybe String
10 | , query : Maybe String
11 | , objectId : Maybe String
12 | , editMode : Bool
13 | }
14 |
15 |
16 | parse : Location -> Result String URL
17 | parse location =
18 | let
19 | floorId =
20 | getFloorId location
21 |
22 | dict =
23 | UrlParser.parseSearch location.search
24 | in
25 | Result.map
26 | (\floorId ->
27 | URL
28 | floorId
29 | (Dict.get "q" dict)
30 | (Dict.get "object" dict)
31 | (Dict.member "edit" dict)
32 | )
33 | floorId
34 |
35 |
36 | getFloorId : Location -> Result String (Maybe String)
37 | getFloorId location =
38 | if String.startsWith "#" location.hash then
39 | let
40 | id =
41 | String.dropLeft 1 location.hash
42 | in
43 | if String.length id == 36 then
44 | Ok (Just id)
45 | else if String.length id == 0 then
46 | Ok Nothing
47 | else
48 | Err ("invalid floorId: " ++ id)
49 | else
50 | Ok Nothing
51 |
52 |
53 | init : URL
54 | init =
55 | { floorId = Nothing
56 | , query = Nothing
57 | , objectId = Nothing
58 | , editMode = False
59 | }
60 |
61 |
62 | stringify : String -> URL -> String
63 | stringify root { floorId, query, objectId, editMode } =
64 | let
65 | params =
66 | (List.filterMap
67 | (\( key, maybeValue ) -> Maybe.map (\v -> ( key, v )) maybeValue)
68 | [ ( "q", query )
69 | , ( "object", objectId )
70 | ]
71 | )
72 | ++ (if editMode then
73 | [ ( "edit", "true" ) ]
74 | else
75 | []
76 | )
77 | in
78 | case floorId of
79 | Just id ->
80 | root ++ stringifyParams params ++ "#" ++ id
81 |
82 | Nothing ->
83 | root ++ stringifyParams params
84 |
85 |
86 | stringifyParams : List ( String, String ) -> String
87 | stringifyParams params =
88 | if params == [] then
89 | ""
90 | else
91 | "?"
92 | ++ (String.join "&" <|
93 | List.map (\( k, v ) -> k ++ "=" ++ v) params
94 | )
95 |
96 |
97 |
98 | --
99 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/PrototypePreviewView.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.PrototypePreviewView exposing (view)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Util.HtmlUtil exposing (..)
6 | import Util.ListUtil exposing (..)
7 | import Model.Prototype exposing (Prototype)
8 | import Model.Prototypes as Prototypes exposing (PositionedPrototype)
9 | import View.Styles as S
10 | import View.PrototypePreviewView as PrototypePreviewView
11 | import Page.Map.Msg exposing (..)
12 | import CoreType exposing (..)
13 |
14 |
15 | containerSize : Size
16 | containerSize =
17 | Size (320 - (20 * 2)) 180
18 |
19 |
20 |
21 | -- TODO
22 |
23 |
24 | view : List ( Prototype, Bool ) -> Html Msg
25 | view prototypes =
26 | let
27 | selectedIndex =
28 | zipWithIndex prototypes
29 | |> List.filterMap
30 | (\( ( prototype, selected ), index ) ->
31 | if selected then
32 | Just index
33 | else
34 | Nothing
35 | )
36 | |> List.head
37 | |> Maybe.withDefault 0
38 |
39 | buttonsView =
40 | buttons (List.length prototypes) selectedIndex
41 |
42 | box =
43 | PrototypePreviewView.view
44 | containerSize
45 | selectedIndex
46 | (List.map Tuple.first prototypes)
47 | in
48 | div
49 | [ style [ ( "position", "relative" ) ] ]
50 | (box :: buttonsView)
51 |
52 |
53 | buttons : Int -> Int -> List (Html Msg)
54 | buttons prototypeLength selectedIndex =
55 | List.map
56 | (\isLeft ->
57 | let
58 | label =
59 | if isLeft then
60 | "<"
61 | else
62 | ">"
63 | in
64 | div
65 | [ style (S.prototypePreviewScroll isLeft)
66 | , onClick_
67 | (if isLeft then
68 | PrototypesMsg Prototypes.prev
69 | else
70 | PrototypesMsg Prototypes.next
71 | )
72 | ]
73 | [ text label ]
74 | )
75 | ((if selectedIndex > 0 then
76 | [ True ]
77 | else
78 | []
79 | )
80 | ++ (if selectedIndex < prototypeLength - 1 then
81 | [ False ]
82 | else
83 | []
84 | )
85 | )
86 |
--------------------------------------------------------------------------------
/persistent-cache/src/Dag.elm:
--------------------------------------------------------------------------------
1 | module Dag
2 | exposing
3 | ( Dag
4 | , Edge
5 | , fromList
6 | , shortestPath
7 | )
8 |
9 | import Dict exposing (Dict)
10 |
11 |
12 | type alias Edge a =
13 | { from : Int
14 | , to : Int
15 | , value : a
16 | }
17 |
18 |
19 | type alias Dag a =
20 | Dict Int (List ( Int, a ))
21 |
22 |
23 |
24 | -- CREATE
25 |
26 |
27 | fromList : List (Edge a) -> Dag a
28 | fromList edges =
29 | List.foldl addNode Dict.empty edges
30 |
31 |
32 | addNode : Edge a -> Dag a -> Dag a
33 | addNode { from, to, value } graph =
34 | let
35 | addNodeHelp maybeNode =
36 | case maybeNode of
37 | Nothing ->
38 | Just [ ( to, value ) ]
39 |
40 | Just kids ->
41 | Just (( to, value ) :: kids)
42 | in
43 | if from < to then
44 | Dict.update from addNodeHelp graph
45 | else
46 | graph
47 |
48 |
49 |
50 | -- SHORTEST PATH
51 |
52 |
53 | shortestPath : Int -> Int -> Dag a -> Maybe (List a)
54 | shortestPath root goal graph =
55 | Maybe.map .path (shortestPathHelp root goal graph)
56 |
57 |
58 | type alias Path a =
59 | { length : Int
60 | , path : List a
61 | }
62 |
63 |
64 | shortestPathHelp : Int -> Int -> Dag a -> Maybe (Path a)
65 | shortestPathHelp root goal graph =
66 | if root == goal then
67 | Just (Path 0 [])
68 | else
69 | case Dict.get root graph of
70 | Nothing ->
71 | Nothing
72 |
73 | Just kids ->
74 | kids
75 | |> List.filterMap (shartestSubPath goal graph)
76 | |> pickShortest Nothing
77 |
78 |
79 | shartestSubPath : Int -> Dag a -> ( Int, a ) -> Maybe (Path a)
80 | shartestSubPath goal graph ( root, value ) =
81 | case shortestPathHelp root goal graph of
82 | Nothing ->
83 | Nothing
84 |
85 | Just { length, path } ->
86 | Just (Path (length + 1) (value :: path))
87 |
88 |
89 | pickShortest : Maybe (Path a) -> List (Path a) -> Maybe (Path a)
90 | pickShortest shortest unexplored =
91 | case unexplored of
92 | [] ->
93 | shortest
94 |
95 | contender :: rest ->
96 | case shortest of
97 | Nothing ->
98 | Just contender
99 |
100 | Just { length } ->
101 | if contender.length < length then
102 | Just contender
103 | else
104 | shortest
105 |
--------------------------------------------------------------------------------
/src/elm/Model/ProfilePopupLogic.elm:
--------------------------------------------------------------------------------
1 | module Model.ProfilePopupLogic exposing (..)
2 |
3 | import Model.Scale as Scale exposing (Scale)
4 | import Model.Object as Object exposing (Object)
5 | import CoreType exposing (..)
6 |
7 |
8 | personPopupSize : Size
9 | personPopupSize =
10 | Size 300 180
11 |
12 |
13 | centerTopScreenXYOfObject : Scale -> Position -> Object -> Position
14 | centerTopScreenXYOfObject scale offset object =
15 | let
16 | { x, y } =
17 | Object.positionOf object
18 |
19 | { width } =
20 | Object.sizeOf object
21 | in
22 | Scale.imageToScreenForPosition
23 | scale
24 | (Position (offset.x + x + width // 2) (offset.y + y))
25 |
26 |
27 | bottomScreenYOfObject : Scale -> Position -> Object -> Int
28 | bottomScreenYOfObject scale offset object =
29 | let
30 | { x, y } =
31 | Object.positionOf object
32 |
33 | { height } =
34 | Object.sizeOf object
35 | in
36 | Scale.imageToScreen scale (offset.y + y + height)
37 |
38 |
39 | calcPopupLeftFromObjectCenter : Int -> Int -> Int
40 | calcPopupLeftFromObjectCenter popupWidth objCenter =
41 | objCenter - (popupWidth // 2)
42 |
43 |
44 | calcPopupRightFromObjectCenter : Int -> Int -> Int
45 | calcPopupRightFromObjectCenter popupWidth objCenter =
46 | objCenter + (popupWidth // 2)
47 |
48 |
49 | calcPopupTopFromObjectTop : Int -> Int -> Int
50 | calcPopupTopFromObjectTop popupHeight objTop =
51 | objTop - (popupHeight + 10)
52 |
53 |
54 | adjustOffset : Size -> Size -> Scale -> Position -> Object -> Position
55 | adjustOffset containerSize popupSize scale offset object =
56 | let
57 | objCenterTop =
58 | centerTopScreenXYOfObject scale offset object
59 |
60 | left =
61 | calcPopupLeftFromObjectCenter popupSize.width objCenterTop.x
62 |
63 | top =
64 | calcPopupTopFromObjectTop popupSize.height objCenterTop.y
65 |
66 | right =
67 | calcPopupRightFromObjectCenter popupSize.width objCenterTop.x
68 |
69 | bottom =
70 | bottomScreenYOfObject scale offset object
71 |
72 | offsetX_ =
73 | adjust scale containerSize.width left right offset.x
74 |
75 | offsetY_ =
76 | adjust scale containerSize.height top bottom offset.y
77 | in
78 | Position offsetX_ offsetY_
79 |
80 |
81 | adjust : Scale -> Int -> Int -> Int -> Int -> Int
82 | adjust scale length min max offset =
83 | if min < 0 then
84 | offset - Scale.screenToImage scale (min - 0)
85 | else if max > length then
86 | offset - Scale.screenToImage scale (max - length)
87 | else
88 | offset
89 |
--------------------------------------------------------------------------------
/Spec.md:
--------------------------------------------------------------------------------
1 | Spec
2 | ====
3 |
4 | ## Routing
5 |
6 | |URL|
7 | |:--|
8 | |/|
9 | |/login|
10 | |/#floorId|
11 | |/?q=foo&edit=#floorId|
12 |
13 |
14 | ## REST API
15 |
16 | (TODO: write by swagger)
17 |
18 | Currently, all of these API are used by client-side program. No public API for other services.
19 |
20 | |Method|URL|Req Body|Res Body|Description|Guest|General|Admin|
21 | |:--|:--|:--|:--|:--|:--|:--|:--|
22 | |GET| /api/1/search/:query||[SearchResult]||✓|✓|✓|
23 | |GET| /api/1/auth||User||✓|✓|✓|
24 | |GET| /api/1/people||[Person]||✓|✓|✓|
25 | |GET| /api/1/people/:personId||Person||✓|✓|✓|
26 | |GET| /api/1/candidates/:name||[Person]||✓|✓|✓|
27 | |GET| /api/1/colors||[Color]||✓|✓|✓|
28 | |GET| /api/1/prototypes||[Prototype]||✓|✓|✓|
29 | |PUT| /api/1/prototypes/:id|Prototype|||||✓|
30 | |DELETE| /api/1/prototypes/:id||||||✓|
31 | |GET| /api/1/floors||[FloorInfo]||✓|✓|✓|
32 | |GET| /api/1/floors?all=true||[FloorInfo]||✓|✓|✓|
33 | |GET| /api/1/floors/:id||Floor|fetch latest version|✓|✓|✓|
34 | |GET| /api/1/floors/:id?all=true||Floor|fetch latest unpublished version||✓|✓|
35 | |PUT| /api/1/floors/:id|FloorChange|Floor|update latest unpublished version||✓|✓|
36 | |PUT| /api/1/floors/:id/public||Floor|publish latest unpublished version|||✓|
37 | |PUT| /api/1/images/:id|Image|||||✓|
38 |
39 |
46 |
47 | ### Types
48 |
49 | |Type|Structure|
50 | |:--|:--|
51 | |User| { id : UUID, name : Person.name, role : Role, personId : String } |
52 | |Floor| { id : UUID, ord : Int, version : Int, name : String, image? : URL, realSize? : (Int, Int), objects : [ Object ], public : Bool, publishedBy? : User.id, publishedAt? : Date } |
53 | |FloorChange| { id : UUID, ord : Int, version : Int, name : String, image? : URL, realSize? : (Int, Int), add : [ Object ], modify : [ (Object, Object) ], delete : [ Object ], public : Bool, publishedBy? : User.id, publishedAt? : Date } |
54 | |Object| { id : UUID, type: String, name : String, size : (Int, Int), color : String, fontSize : Float, shape : String, personId? : String } |
55 | |Prototype| { id : UUID, name : String, size : (Int, Int), color : String } |
56 | |Image| Binary |
57 | |SearchResult| [(Object, String)] |
58 | |Person| { id : String, name : String, post : String, tel? : String, mail? : String, image? : URL } |
59 | |Role| "admin" "general" |
60 | |Color| { id : String, ord : Int, type : String, color : String } |
61 | |UUID| String |
62 | |Date| Int |
63 | |URL| String |
64 |
65 |
66 | ### Table Definition
67 |
68 | See [here](server/sql/2-create-tables.sql).
69 |
--------------------------------------------------------------------------------
/src/elm/Model/DateFormatter.elm:
--------------------------------------------------------------------------------
1 | module Model.DateFormatter exposing (formatDate, formatDateOrTime)
2 |
3 | import Date exposing (..)
4 | import Model.I18n exposing (Language(..))
5 |
6 |
7 | monthToInt : Month -> Int
8 | monthToInt month =
9 | case month of
10 | Jan ->
11 | 1
12 |
13 | Feb ->
14 | 2
15 |
16 | Mar ->
17 | 3
18 |
19 | Apr ->
20 | 4
21 |
22 | May ->
23 | 5
24 |
25 | Jun ->
26 | 6
27 |
28 | Jul ->
29 | 7
30 |
31 | Aug ->
32 | 8
33 |
34 | Sep ->
35 | 9
36 |
37 | Oct ->
38 | 10
39 |
40 | Nov ->
41 | 11
42 |
43 | Dec ->
44 | 12
45 |
46 |
47 | sameDay : Date -> Date -> Bool
48 | sameDay d1 d2 =
49 | year d1
50 | == year d2
51 | && month d1
52 | == month d2
53 | && day d1
54 | == day d2
55 |
56 |
57 | am : Date -> Bool
58 | am date =
59 | if hour date < 12 then
60 | True
61 | else
62 | False
63 |
64 |
65 | pm : Date -> Bool
66 | pm =
67 | not << am
68 |
69 |
70 | hourOfAmPm : Int -> Int
71 | hourOfAmPm hour =
72 | if hour > 12 then
73 | hour - 12
74 | else
75 | hour
76 |
77 |
78 | ampm : Date -> String
79 | ampm date =
80 | toString (hourOfAmPm (hour date))
81 | ++ ":"
82 | ++ fillZero2 (toString (minute date))
83 | ++ " "
84 | ++ (if am date then
85 | "a.m."
86 | else
87 | "p.m."
88 | )
89 |
90 |
91 | formatTime : Language -> Date -> String
92 | formatTime lang date =
93 | case lang of
94 | JA ->
95 | fillZero2 (toString (hour date))
96 | ++ ":"
97 | ++ fillZero2 (toString (minute date))
98 |
99 | EN ->
100 | ampm date
101 |
102 |
103 | fillZero2 : String -> String
104 | fillZero2 s =
105 | String.right 2 ("0" ++ s)
106 |
107 |
108 | formatDate : Language -> Date -> String
109 | formatDate lang date =
110 | case lang of
111 | JA ->
112 | toString (Date.year date)
113 | ++ "/"
114 | ++ toString (monthToInt <| Date.month date)
115 | ++ "/"
116 | ++ toString (Date.day date)
117 |
118 | EN ->
119 | toString (monthToInt <| Date.month date)
120 | ++ "/"
121 | ++ toString (Date.day date)
122 | ++ "/"
123 | ++ toString (Date.year date)
124 |
125 |
126 | formatDateOrTime : Language -> Date -> Date -> String
127 | formatDateOrTime lang now date =
128 | if sameDay now date then
129 | formatTime lang date
130 | else
131 | formatDate lang date
132 |
--------------------------------------------------------------------------------
/src/elm/API/Cache.elm:
--------------------------------------------------------------------------------
1 | module API.Cache exposing (..)
2 |
3 | import Task exposing (..)
4 | import Json.Encode as E exposing (Value)
5 | import Json.Decode as D exposing (Decoder)
6 | import PersistentCache as Cache
7 | import Model.Scale as Scale exposing (Scale)
8 | import Model.I18n as I18n exposing (..)
9 | import Util.DecodeUtil exposing (..)
10 |
11 |
12 | type alias Cache =
13 | Cache.Cache UserState
14 |
15 |
16 | cache : Cache
17 | cache =
18 | Cache.cache
19 | { name = "userState"
20 | , version = 1
21 | , kilobytes = 1024
22 | , decode = decode
23 | , encode = encode
24 | }
25 |
26 |
27 | defaultUserState : Language -> UserState
28 | defaultUserState lang =
29 | { scale = Scale.default
30 | , offset = { x = 35, y = 35 }
31 | , lang = lang
32 | }
33 |
34 |
35 | get : Cache -> Task x (Maybe UserState)
36 | get cache =
37 | Cache.get cache "userState"
38 |
39 |
40 | getWithDefault : Cache -> UserState -> Task x UserState
41 | getWithDefault cache defaultState =
42 | Cache.get cache "userState"
43 | |> andThen
44 | (\maybeState ->
45 | case maybeState of
46 | Just state ->
47 | Task.succeed state
48 |
49 | Nothing ->
50 | put cache defaultState
51 | |> Task.map (\_ -> defaultState)
52 | )
53 |
54 |
55 | put : Cache -> UserState -> Task x ()
56 | put cache state =
57 | Cache.add cache "userState" state
58 |
59 |
60 | clear : Cache -> Task x ()
61 | clear cache =
62 | Cache.clear cache
63 |
64 |
65 | type alias Position =
66 | { x : Int
67 | , y : Int
68 | }
69 |
70 |
71 | type alias UserState =
72 | { scale : Scale
73 | , offset : Position
74 | , lang : Language
75 | }
76 |
77 |
78 | decode : Decoder UserState
79 | decode =
80 | D.map3
81 | (\scale ( x, y ) lang ->
82 | { scale = Scale.init scale
83 | , offset = { x = x, y = y }
84 | , lang =
85 | if lang == "JA" then
86 | I18n.JA
87 | else
88 | I18n.EN
89 | }
90 | )
91 | (D.field "scale" D.int)
92 | (D.field "offset" <| tuple2 (,) D.int D.int)
93 | (D.field "lang" D.string)
94 |
95 |
96 | encode : UserState -> Value
97 | encode state =
98 | E.object
99 | [ ( "scale", E.int state.scale.scaleDown )
100 | , ( "offset", E.list [ E.int state.offset.x, E.int state.offset.y ] )
101 | , ( "lang"
102 | , E.string <|
103 | case state.lang of
104 | JA ->
105 | "JA"
106 |
107 | EN ->
108 | "EN"
109 | )
110 | ]
111 |
--------------------------------------------------------------------------------
/server/lib/search-optimizer.js:
--------------------------------------------------------------------------------
1 | // https://gist.github.com/kawanet/5553478
2 |
3 | function normalizeKana(src) {
4 | return src.replace(/[\u30a1-\u30f6]/g, function(match) {
5 | var chr = match.charCodeAt(0) - 0x60;
6 | return String.fromCharCode(chr);
7 | });
8 | }
9 |
10 | // http://www13.plala.or.jp/bigdata/kanji_2.html
11 | function normalizeKanji(src) {
12 | return src
13 | .replace(/[髙]/g, '高')
14 | .replace(/[槗𣘺𣘺嵜]/g, '橋')
15 | .replace(/[斎齊齋]/g, '斉')
16 | .replace(/[籐]/g, '藤')
17 | .replace(/[邊邉邉󠄂邉󠄃邉󠄄邉󠄅邉󠄆邉󠄇邉󠄈邉󠄉邉󠄊邉󠄋邉󠄌邉󠄍邉󠄎邊󠄁邊󠄂邊󠄃邊󠄄邊󠄅邊󠄆邊󠄇]/g, '辺')
18 | .replace(/[𠮷]/g, '吉')
19 | .replace(/[濱濵]/g, '浜')
20 | .replace(/[﨑碕嵜]/g, '崎');
21 | }
22 |
23 | function normalizeRoma(src) {
24 | return src
25 | .replace(/shi/g, 'si')
26 | .replace(/chi/g, 'ti')
27 | .replace(/tsu/g, 'tu')
28 | .replace(/sya/g, 'sha')
29 | .replace(/syu/g, 'shu')
30 | .replace(/syo/g, 'sho')
31 | .replace(/tya/g, 'cha')
32 | .replace(/tyu/g, 'chu')
33 | .replace(/tyo/g, 'cho');
34 | }
35 |
36 | // http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/u2460.html
37 | function normalizeNumber(src) {
38 | return src
39 | .replace(/[\u2460-\u2473]/g, function(match) {
40 | return (match.charCodeAt(0) - 0x2460 + 1) + '';
41 | })
42 | .replace(/[\u2474-\u2487]/g, function(match) {
43 | return (match.charCodeAt(0) - 0x2474 + 1) + '';
44 | })
45 | .replace(/[\u2488-\u249b]/g, function(match) {
46 | return (match.charCodeAt(0) - 0x2488 + 1) + '';
47 | });
48 | }
49 |
50 | // http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html
51 | function normalizeFullWidth(src) {
52 | return src.replace(/[\uff01-\uff5e]/g, function(match) {
53 | var chr = match.charCodeAt(0) - 0xff01 + 0x21;
54 | return String.fromCharCode(chr);
55 | });
56 | }
57 |
58 | // https://github.com/jinjor/ja-en-separator/blob/master/package/content-script.js
59 | function separateJaEn(src) {
60 | return src
61 | .replace(/ ?([^\x01-\x7E]+) ?/g, ' $1 ')
62 | .replace(/^ /, '')
63 | .replace(/ $/, '');
64 | }
65 |
66 | function normalizeSpace(src) {
67 | return src
68 | .replace(/[ \r\n\t]+/g, ' ')
69 | .trim()
70 | .replace(/[ ]+/g, ' ');
71 | }
72 |
73 | function normalize(src) {
74 | var tmp = src;
75 | tmp = normalizeFullWidth(tmp);
76 | tmp = normalizeNumber(tmp);
77 | tmp = tmp.toLowerCase();
78 | tmp = normalizeKana(tmp);
79 | tmp = normalizeKanji(tmp);
80 | tmp = normalizeRoma(tmp);
81 | tmp = separateJaEn(tmp);
82 | tmp = normalizeSpace(tmp);
83 | return tmp;
84 | }
85 |
86 | function tokenize(src) {
87 | return src.split(/[ \//・]+/g);
88 | }
89 |
90 | function getKeys(object) {
91 | var normalized = normalize(object.name);
92 | var tokens = tokenize(normalized);
93 | return tokens;
94 | }
95 |
96 | module.exports = {
97 | normalize: normalize,
98 | getKeys: getKeys
99 | };
100 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/ClipboardOptionsView.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.ClipboardOptionsView exposing (Form, init, view)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes as Attributes exposing (..)
5 | import Html.Events exposing (..)
6 | import CoreType exposing (..)
7 | import View.Styles as Styles
8 |
9 |
10 | type alias Form =
11 | { width : Maybe String
12 | , height : Maybe String
13 | }
14 |
15 |
16 | init : Form
17 | init =
18 | Form Nothing Nothing
19 |
20 |
21 | maybeError : Result e a -> Maybe e
22 | maybeError result =
23 | case result of
24 | Ok _ ->
25 | Nothing
26 |
27 | Err message ->
28 | Just message
29 |
30 |
31 | type Msg
32 | = ChangeWidth (Result String Int)
33 | | ChangeHeight (Result String Int)
34 |
35 |
36 | update : Size -> Form -> Msg -> ( Form, Maybe Size )
37 | update size form msg =
38 | case msg of
39 | ChangeWidth result ->
40 | case result of
41 | Ok width ->
42 | ( { form | width = Nothing }, Just { size | width = width } )
43 |
44 | Err message ->
45 | ( { form | width = Just message }, Nothing )
46 |
47 | ChangeHeight result ->
48 | case result of
49 | Ok height ->
50 | ( { form | height = Nothing }, Just { size | height = height } )
51 |
52 | Err message ->
53 | ( { form | height = Just message }, Nothing )
54 |
55 |
56 | view : (( Form, Maybe Size ) -> msg) -> Form -> Size -> Html msg
57 | view tagger form size =
58 | let
59 | widthLabel =
60 | label [ style Styles.widthHeightLabel ] [ text "幅" ]
61 |
62 | heightLabel =
63 | label [ style Styles.widthHeightLabel ] [ text "高さ" ]
64 | in
65 | div []
66 | [ div [ style [ ( "font-size", "13px" ) ] ] [ text "スプレッドシート貼付オプション" ]
67 | , div
68 | [ style Styles.floorSizeInputContainer ]
69 | [ widthLabel
70 | , sizeInput size.width |> Html.map ChangeWidth
71 | , heightLabel
72 | , sizeInput size.height |> Html.map ChangeHeight
73 | ]
74 | |> Html.map (update size form >> tagger)
75 | ]
76 |
77 |
78 | sizeInput : Int -> Html (Result String Int)
79 | sizeInput intValue =
80 | input
81 | [ style Styles.realSizeInput
82 | , value (toString intValue)
83 | , onInput validate
84 | ]
85 | []
86 |
87 |
88 | validate : String -> Result String Int
89 | validate value =
90 | value
91 | |> String.toInt
92 | |> Result.mapError (always "only numbers are allowed")
93 | |> Result.andThen validateInt
94 |
95 |
96 | validateInt : Int -> Result String Int
97 | validateInt i =
98 | if i > 0 then
99 | Ok i
100 | else
101 | Err "only positive numbers are allowed"
102 |
--------------------------------------------------------------------------------
/src/elm/Model/Mode.elm:
--------------------------------------------------------------------------------
1 | module Model.Mode exposing (..)
2 |
3 |
4 | type Mode
5 | = Mode
6 | { printMode : Bool
7 | , searchResult : Bool
8 | , editing : Bool
9 | , editMode : EditingMode
10 | }
11 |
12 |
13 | type EditingMode
14 | = Select
15 | | Pen
16 | | Stamp
17 | | Label
18 |
19 |
20 | init : Bool -> Mode
21 | init isEditMode =
22 | Mode
23 | { printMode = False
24 | , searchResult = False
25 | , editing = isEditMode
26 | , editMode = Select
27 | }
28 |
29 |
30 | showingSearchResult : Mode -> Bool
31 | showingSearchResult (Mode mode) =
32 | mode.searchResult
33 |
34 |
35 | toggleEditing : Mode -> Mode
36 | toggleEditing (Mode mode) =
37 | Mode
38 | { mode
39 | | editing = not (mode.editing)
40 | }
41 |
42 |
43 | togglePrintView : Mode -> Mode
44 | togglePrintView (Mode mode) =
45 | Mode
46 | { mode
47 | | printMode = not (mode.printMode)
48 | }
49 |
50 |
51 | toSelectMode : Mode -> Mode
52 | toSelectMode (Mode mode) =
53 | Mode
54 | { mode
55 | | editing = True
56 | , editMode = Select
57 | }
58 |
59 |
60 | toStampMode : Mode -> Mode
61 | toStampMode (Mode mode) =
62 | Mode
63 | { mode
64 | | editing = True
65 | , editMode = Stamp
66 | }
67 |
68 |
69 | isEditMode : Mode -> Bool
70 | isEditMode ((Mode mode) as mode_) =
71 | mode.editing && not (isPrintMode mode_)
72 |
73 |
74 | currentEditMode : Mode -> Maybe EditingMode
75 | currentEditMode (Mode mode) =
76 | if mode.editing then
77 | Just mode.editMode
78 | else
79 | Nothing
80 |
81 |
82 | isSelectMode : Mode -> Bool
83 | isSelectMode (Mode mode) =
84 | mode.editing && mode.editMode == Select
85 |
86 |
87 | isPenMode : Mode -> Bool
88 | isPenMode (Mode mode) =
89 | mode.editing && mode.editMode == Pen
90 |
91 |
92 | isStampMode : Mode -> Bool
93 | isStampMode (Mode mode) =
94 | mode.editing && mode.editMode == Stamp
95 |
96 |
97 | isLabelMode : Mode -> Bool
98 | isLabelMode (Mode mode) =
99 | mode.editing && mode.editMode == Label
100 |
101 |
102 | isViewMode : Mode -> Bool
103 | isViewMode (Mode mode) =
104 | not mode.editing
105 |
106 |
107 | isPrintMode : Mode -> Bool
108 | isPrintMode (Mode mode) =
109 | mode.printMode
110 |
111 |
112 | changeEditingMode : EditingMode -> Mode -> Mode
113 | changeEditingMode editingMode (Mode mode) =
114 | Mode
115 | { mode
116 | | editing = True
117 | , editMode = editingMode
118 | }
119 |
120 |
121 | showSearchResult : Mode -> Mode
122 | showSearchResult (Mode mode) =
123 | Mode
124 | { mode
125 | | searchResult = True
126 | }
127 |
128 |
129 | hideSearchResult : Mode -> Mode
130 | hideSearchResult (Mode mode) =
131 | Mode
132 | { mode
133 | | searchResult = False
134 | }
135 |
--------------------------------------------------------------------------------
/server/lib/schema.js:
--------------------------------------------------------------------------------
1 | function floorKeyValues(tenantId, floor, updateAt) {
2 | return [
3 | ["id", floor.id],
4 | ["version", floor.version],
5 | ["tenantId", tenantId],
6 | ["name", floor.name],
7 | ["ord", floor.ord],
8 | ["image", floor.image],
9 | ["flipImage", floor.flipImage ? 1 : 0],
10 | ["width", floor.width],
11 | ["height", floor.height],
12 | ["realWidth", floor.realWidth],
13 | ["realHeight", floor.realHeight],
14 | ["temporary", floor.temporary ? 1 : 0],
15 | ["updateBy", floor.updateBy],
16 | ["updateAt", updateAt]
17 | ];
18 | }
19 |
20 | function prototypeKeyValues(tenantId, proto) {
21 | return [
22 | ["id", proto.id],
23 | ["tenantId", tenantId],
24 | ["name", proto.name],
25 | ["width", proto.width],
26 | ["height", proto.height],
27 | ["backgroundColor", proto.backgroundColor],
28 | ["color", proto.color],
29 | ["fontSize", proto.fontSize],
30 | ["shape", proto.shape]
31 | ];
32 | }
33 |
34 | function objectKeyValues(object, updateAt) {
35 | return [
36 | ["id", object.id],
37 | ["name", object.name],
38 | ["type", object.type],
39 | ["x", object.x],
40 | ["y", object.y],
41 | ["width", object.width],
42 | ["height", object.height],
43 | ["backgroundColor", object.backgroundColor],
44 | ["fontSize", object.fontSize],
45 | ["color", object.color],
46 | ["bold", object.bold ? 1 : 0],
47 | ["url", object.url],
48 | ["shape", object.shape],
49 | ["personId", object.personId],
50 | ["floorId", object.floorId],
51 | ["floorVersion", object.floorVersion],
52 | ["updateAt", updateAt]
53 | ];
54 | }
55 |
56 | function objectOptKeyValues(object) {
57 | return [
58 | ["id", object.id],
59 | ["name", object.name],
60 | ["type", object.type],
61 | ["x", object.x],
62 | ["y", object.y],
63 | ["width", object.width],
64 | ["height", object.height],
65 | ["backgroundColor", object.backgroundColor],
66 | ["fontSize", object.fontSize],
67 | ["color", object.color],
68 | ["bold", object.bold ? 1 : 0],
69 | ["url", object.url],
70 | ["shape", object.shape],
71 | ["personId", object.personId],
72 | ["personName", object.personName || ''],
73 | ["personEmpNo", object.personEmpNo || ''],
74 | ["personPost", object.personPost || ''],
75 | ["personTel1", object.personTel1 || ''],
76 | ["personTel2", object.personTel2 || ''],
77 | ["personMail", object.personMail || ''],
78 | ["personImage", object.personImage || ''],
79 | ["floorId", object.floorId],
80 | ["editing", object.editing ? 1 : 0],
81 | ["updateAt", object.updateAt]
82 | ];
83 | }
84 |
85 |
86 | function colorKeyValues(tenantId, c) {
87 | return [
88 | ["id", c.id],
89 | ["tenantId", tenantId],
90 | ["ord", c.ord],
91 | ["type", c.type],
92 | ["color", c.color]
93 | ];
94 | }
95 |
96 | module.exports = {
97 | floorKeyValues: floorKeyValues,
98 | prototypeKeyValues: prototypeKeyValues,
99 | objectKeyValues: objectKeyValues,
100 | objectOptKeyValues: objectOptKeyValues,
101 | colorKeyValues: colorKeyValues
102 | };
103 |
--------------------------------------------------------------------------------
/server/commands.js:
--------------------------------------------------------------------------------
1 | var db = require('./lib/db.js');
2 | var rdb = require('./lib/mysql.js');
3 | var mock = require('./lib/mock.js');
4 | var config = require('./lib/config.js');
5 | var accountService = require('./lib/account-service.js');
6 |
7 | var rdbEnv = rdb.createEnv(config.mysql.host, config.mysql.user, config.mysql.pass, 'map2');
8 |
9 | function login() {
10 | if (!config.operationUser && !config.operationPass) {
11 | // server don't need token.
12 | return Promise.resolve(null);
13 | } else {
14 | // debugging user may need token.
15 | return accountService.login(config.accountServiceRoot, config.operationUser, config.operationPass);
16 | }
17 | }
18 |
19 | var commands = {};
20 |
21 | commands.createObjectOptTable = function() {
22 | return login().then(token => {
23 | return rdbEnv.forConnectionAndTransaction(conn => {
24 | console.log(`creating optimized object table ...`);
25 | return db.createObjectOptTable(conn, config.profileServiceRoot, token, true).then(_ => {
26 | return db.createObjectOptTable(conn, config.profileServiceRoot, token, false);
27 | });
28 | });
29 | });
30 | }
31 |
32 | commands.createInitialData = function(tenantId) {
33 | tenantId = tenantId || '';
34 | if (config.multiTenency && !tenantId) {
35 | return Promise.reject('tenantId is not defined.');
36 | }
37 | return rdbEnv.forConnectionAndTransaction(conn => {
38 | console.log(`creating data for tenant ${tenantId} ...`);
39 | return db.savePrototypes(conn, tenantId, mock.prototypes).then(() => {
40 | return db.saveColors(conn, tenantId, mock.colors);
41 | });
42 | }).then(rdbEnv.end);
43 | };
44 |
45 | commands.deleteFloor = function(floorId) {
46 | if (!floorId) {
47 | return Promise.reject('floorId is not defined.');
48 | }
49 | var tenantId = '';
50 | return rdbEnv.forConnectionAndTransaction(conn => {
51 | console.log('deleting floor ' + floorId + '...');
52 | return db.deleteFloorWithObjects(conn, tenantId, floorId);
53 | }).then(rdbEnv.end);
54 | };
55 |
56 | commands.deletePrototype = function(id) {
57 | if (!id) {
58 | return Promise.reject('id is not defined.');
59 | }
60 | var tenantId = '';
61 | return rdbEnv.forConnectionAndTransaction(conn => {
62 | console.log('deleting prototype ' + id + '...');
63 | return db.deletePrototype(conn, tenantId, id);
64 | }).then(rdbEnv.end);
65 | };
66 |
67 | commands.resetImage = function() {
68 | console.log('reseting image ...');
69 | return db.resetImage(null, 'images/floors');
70 | };
71 |
72 | //------------------
73 | // usage:
74 | //
75 | // node server/commands.js funcName
76 | //
77 | //
78 |
79 | var args = process.argv;
80 | args.shift(); // node
81 | args.shift(); // server/commands.js
82 | var funcName = args.shift();
83 | if (funcName) {
84 | try {
85 | commands[funcName].apply(null, args).then(() => {
86 | console.log('done');
87 | process.exit(0);
88 | }).catch((e) => {
89 | console.log(e);
90 | console.log(e.stack);
91 | process.exit(1);
92 | });
93 | } catch (e) {
94 | console.log(e);
95 | console.log(e.stack);
96 | process.exit(1);
97 | }
98 | }
99 |
100 | module.exports = commands;
101 |
--------------------------------------------------------------------------------
/src/elm/Model/FloorInfo.elm:
--------------------------------------------------------------------------------
1 | module Model.FloorInfo exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import CoreType exposing (..)
5 | import Model.Floor exposing (FloorBase)
6 |
7 |
8 | type FloorInfo
9 | = FloorInfo (Maybe FloorBase) FloorBase
10 |
11 |
12 | init : Maybe FloorBase -> FloorBase -> FloorInfo
13 | init publicFloor editingFloor =
14 | FloorInfo publicFloor editingFloor
15 |
16 |
17 | isNeverPublished : FloorInfo -> Bool
18 | isNeverPublished floorsInfo =
19 | case floorsInfo of
20 | FloorInfo Nothing _ ->
21 | True
22 |
23 | _ ->
24 | False
25 |
26 |
27 | idOf : FloorInfo -> FloorId
28 | idOf (FloorInfo publicFloor editingFloor) =
29 | editingFloor.id
30 |
31 |
32 | publicFloor : FloorInfo -> Maybe FloorBase
33 | publicFloor (FloorInfo publicFloor editingFloor) =
34 | publicFloor
35 |
36 |
37 | editingFloor : FloorInfo -> FloorBase
38 | editingFloor (FloorInfo publicFloor editingFloor) =
39 | editingFloor
40 |
41 |
42 | findPublicFloor : FloorId -> Dict FloorId FloorInfo -> Maybe FloorBase
43 | findPublicFloor floorId floorsInfo =
44 | floorsInfo
45 | |> findFloor floorId
46 | |> Maybe.andThen publicFloor
47 |
48 |
49 | findFloor : FloorId -> Dict FloorId FloorInfo -> Maybe FloorInfo
50 | findFloor floorId floorsInfo =
51 | floorsInfo
52 | |> Dict.get floorId
53 |
54 |
55 | mergeFloor : FloorBase -> Dict FloorId FloorInfo -> Dict FloorId FloorInfo
56 | mergeFloor editingFloor floorsInfo =
57 | floorsInfo
58 | |> Dict.update editingFloor.id (Maybe.map (mergeFloorHelp editingFloor))
59 |
60 |
61 | mergeFloorHelp : FloorBase -> FloorInfo -> FloorInfo
62 | mergeFloorHelp floor (FloorInfo publicFloor editingFloor) =
63 | if floor.version < 0 then
64 | FloorInfo publicFloor floor
65 | else
66 | FloorInfo (Just floor) editingFloor
67 |
68 |
69 | toValues : Dict FloorId FloorInfo -> List FloorInfo
70 | toValues floorsInfo =
71 | floorsInfo
72 | |> Dict.toList
73 | |> List.map Tuple.second
74 |
75 |
76 | toPublicList : Dict FloorId FloorInfo -> List FloorBase
77 | toPublicList floorsInfo =
78 | floorsInfo
79 | |> toValues
80 | |> List.filterMap publicFloor
81 | |> List.sortBy .ord
82 |
83 |
84 | toEditingList : Dict FloorId FloorInfo -> List FloorBase
85 | toEditingList floorsInfo =
86 | floorsInfo
87 | |> toValues
88 | |> List.map editingFloor
89 | |> List.sortWith
90 | (\f1 f2 ->
91 | if f1.temporary == f2.temporary then
92 | intToOrd f1.ord f2.ord
93 | else if f2.temporary then
94 | GT
95 | else
96 | LT
97 | )
98 |
99 |
100 | intToOrd : Int -> Int -> Order
101 | intToOrd i1 i2 =
102 | if i1 > i2 then
103 | GT
104 | else if i1 < i2 then
105 | LT
106 | else
107 | EQ
108 |
109 |
110 | sortByPublicOrder : Dict FloorId FloorInfo -> Maybe FloorInfo
111 | sortByPublicOrder floorsInfo =
112 | floorsInfo
113 | |> toValues
114 | |> List.sortBy (\info -> publicFloor info |> Maybe.map .ord |> Maybe.withDefault 999999)
115 | |> List.head
116 |
--------------------------------------------------------------------------------
/src/elm/View/Icons.elm:
--------------------------------------------------------------------------------
1 | module View.Icons exposing (..)
2 |
3 | import Svg exposing (Svg)
4 | import Color exposing (Color, white, black, gray)
5 | import FontAwesome exposing (..)
6 |
7 |
8 | defaultColor : Color
9 | defaultColor =
10 | Color.rgb 140 140 140
11 |
12 |
13 | modeColor : Color
14 | modeColor =
15 | Color.rgb 90 90 90
16 |
17 |
18 | link : Svg msg
19 | link =
20 | FontAwesome.link defaultColor 16
21 |
22 |
23 | mode : (Color -> Int -> Svg msg) -> (Bool -> Svg msg)
24 | mode f =
25 | \selected ->
26 | f
27 | (if selected then
28 | white
29 | else
30 | modeColor
31 | )
32 | 24
33 |
34 |
35 | selectMode : Bool -> Svg msg
36 | selectMode =
37 | mode mouse_pointer
38 |
39 |
40 | penMode : Bool -> Svg msg
41 | penMode =
42 | mode pencil
43 |
44 |
45 | stampMode : Bool -> Svg msg
46 | stampMode =
47 | mode th_large
48 |
49 |
50 | labelMode : Bool -> Svg msg
51 | labelMode =
52 | mode font
53 |
54 |
55 | personMatched : Float -> Svg msg
56 | personMatched ratio =
57 | check white (Basics.floor (18 * ratio))
58 |
59 |
60 | personNotMatched : Float -> Svg msg
61 | personNotMatched ratio =
62 | question white (Basics.floor (18 * ratio))
63 |
64 |
65 | popupClose : Svg msg
66 | popupClose =
67 | close defaultColor 18
68 |
69 |
70 | searchResultClose : Svg msg
71 | searchResultClose =
72 | close defaultColor 18
73 |
74 |
75 | proplabelColor : Color
76 | proplabelColor =
77 | defaultColor
78 |
79 |
80 | backgroundColorPropLabel : Svg msg
81 | backgroundColorPropLabel =
82 | th_large proplabelColor 12
83 |
84 |
85 | colorPropLabel : Svg msg
86 | colorPropLabel =
87 | font proplabelColor 12
88 |
89 |
90 | shapePropLabel : Svg msg
91 | shapePropLabel =
92 | star_o proplabelColor 12
93 |
94 |
95 | fontSizePropLabel : Svg msg
96 | fontSizePropLabel =
97 | font proplabelColor 12
98 |
99 |
100 | shapeRectangle : Svg msg
101 | shapeRectangle =
102 | square defaultColor 20
103 |
104 |
105 | shapeEllipse : Svg msg
106 | shapeEllipse =
107 | circle defaultColor 20
108 |
109 |
110 | searchResultItemPerson : Svg msg
111 | searchResultItemPerson =
112 | user defaultColor 20
113 |
114 |
115 | searchResultItemPost : Svg msg
116 | searchResultItemPost =
117 | user defaultColor 20
118 |
119 |
120 | personDetailPopupPersonTel : Svg msg
121 | personDetailPopupPersonTel =
122 | phone defaultColor 16
123 |
124 |
125 | personDetailPopupPersonMail : Svg msg
126 | personDetailPopupPersonMail =
127 | envelope defaultColor 16
128 |
129 |
130 | headerIconColor : Color
131 | headerIconColor =
132 | white
133 |
134 |
135 | editingToggle : Svg msg
136 | editingToggle =
137 | pencil headerIconColor 22
138 |
139 |
140 | printButton : Bool -> Svg msg
141 | printButton printMode =
142 | print
143 | (if printMode then
144 | defaultColor
145 | else
146 | headerIconColor
147 | )
148 | 22
149 |
150 |
151 | helpButton : Svg msg
152 | helpButton =
153 | question_circle headerIconColor 22
154 |
155 |
156 | userMenuToggle : Bool -> Svg msg
157 | userMenuToggle open =
158 | (if open then
159 | caret_up
160 | else
161 | caret_down
162 | )
163 | white
164 | 16
165 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/KeyOperation.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.KeyOperation exposing (..)
2 |
3 | import Char
4 | import Json.Decode as Decode exposing (Decoder)
5 | import CoreType exposing (..)
6 | import Page.Map.Msg exposing (Msg(..))
7 |
8 |
9 | c : Int
10 | c =
11 | Char.toCode 'C'
12 |
13 |
14 | x : Int
15 | x =
16 | Char.toCode 'X'
17 |
18 |
19 | y : Int
20 | y =
21 | Char.toCode 'Y'
22 |
23 |
24 | z : Int
25 | z =
26 | Char.toCode 'Z'
27 |
28 |
29 | left : Int
30 | left =
31 | 37
32 |
33 |
34 | up : Int
35 | up =
36 | 38
37 |
38 |
39 | right : Int
40 | right =
41 | 39
42 |
43 |
44 | down : Int
45 | down =
46 | 40
47 |
48 |
49 | backSpace : Int
50 | backSpace =
51 | 8
52 |
53 |
54 | delete : Int
55 | delete =
56 | 46
57 |
58 |
59 | tab : Int
60 | tab =
61 | 9
62 |
63 |
64 | ctrl : Int
65 | ctrl =
66 | 17
67 |
68 |
69 | isCommand : Int -> Bool
70 | isCommand keyCode =
71 | (keyCode == 224)
72 | || (keyCode == 17)
73 | || (keyCode == 91)
74 | || (keyCode == 93)
75 |
76 |
77 | isCtrlOrCommand : Int -> Bool
78 | isCtrlOrCommand keyCode =
79 | keyCode == ctrl || isCommand keyCode
80 |
81 |
82 | decodeOperation : Decoder Msg
83 | decodeOperation =
84 | decodeOperationHelp toMsg
85 |
86 |
87 | toMsg : Bool -> Bool -> Int -> Maybe Msg
88 | toMsg ctrl shift keyCode =
89 | if ctrl && keyCode == c then
90 | Just Copy
91 | else if ctrl && keyCode == x then
92 | Just Cut
93 | else if ctrl && keyCode == y then
94 | Just Redo
95 | else if ctrl && keyCode == z then
96 | Just Undo
97 | else if keyCode == delete || keyCode == backSpace then
98 | Just Delete
99 | else if shift && keyCode == up then
100 | Just (ExpandOrShrinkToward Up)
101 | else if shift && keyCode == down then
102 | Just (ExpandOrShrinkToward Down)
103 | else if shift && keyCode == left then
104 | Just (ExpandOrShrinkToward Left)
105 | else if shift && keyCode == right then
106 | Just (ExpandOrShrinkToward Right)
107 | else if keyCode == up then
108 | Just (MoveSelecedObjectsToward Up)
109 | else if keyCode == down then
110 | Just (MoveSelecedObjectsToward Down)
111 | else if keyCode == left then
112 | Just (MoveSelecedObjectsToward Left)
113 | else if keyCode == right then
114 | Just (MoveSelecedObjectsToward Right)
115 | else if keyCode == tab then
116 | Just ShiftSelectionByTab
117 | else
118 | Nothing
119 |
120 |
121 | decodeOperationHelp : (Bool -> Bool -> Int -> Maybe msg) -> Decoder msg
122 | decodeOperationHelp toMsg =
123 | Decode.map3 toMsg
124 | decodeCtrlOrCommand
125 | decodeShift
126 | decodeKeyCode
127 | |> Decode.andThen
128 | (\maybeMsg ->
129 | maybeMsg
130 | |> Maybe.map Decode.succeed
131 | |> Maybe.withDefault (Decode.fail "")
132 | )
133 |
134 |
135 | decodeShift : Decoder Bool
136 | decodeShift =
137 | Decode.field "shiftKey" Decode.bool
138 |
139 |
140 | decodeCtrlOrCommand : Decoder Bool
141 | decodeCtrlOrCommand =
142 | Decode.map2 (||)
143 | (Decode.field "ctrlKey" Decode.bool)
144 | (Decode.field "metaKey" Decode.bool)
145 |
146 |
147 | decodeKeyCode : Decoder Int
148 | decodeKeyCode =
149 | Decode.field "keyCode" Decode.int
150 |
--------------------------------------------------------------------------------
/server/lib/mysql.js:
--------------------------------------------------------------------------------
1 | var mysql = require('mysql');
2 | var log = require('./log.js');
3 |
4 | function exec(conn, sql) {
5 | return new Promise((resolve, reject) => {
6 | conn.query(sql, (e, rows, fields) => {
7 | if (e) {
8 | log.system.error('SQL failed: ' + sql.split('\n').join(), e.message);
9 | reject(e);
10 | } else {
11 | (rows.length ? rows : []).forEach((row) => {
12 | (fields || []).forEach((field) => {
13 | if (field.type === 1) {
14 | row[field.name] = !!row[field.name];
15 | }
16 | });
17 | });
18 | var _res = rows.length || rows.affectedRows || '';
19 | log.system.debug(`${sql.split('\n').join()} => ${_res}`);
20 | resolve(rows);
21 | }
22 | });
23 | });
24 | }
25 |
26 | function one(conn, sql) {
27 | return exec(conn, sql).then((list) => {
28 | return Promise.resolve(list[0]);
29 | });
30 | }
31 |
32 | function batch(conn, list) {
33 | var promises = list.map((sql) => {
34 | return exec(conn, sql);
35 | });
36 | return promises.reduce((promise, next) => {
37 | return promise.then((result) => next);
38 | }, Promise.resolve());
39 | }
40 |
41 | function beginTransaction(conn) {
42 | return new Promise((resolve, reject) => {
43 | conn.beginTransaction((e) => {
44 | if (e) {
45 | reject(e);
46 | } else {
47 | resolve();
48 | }
49 | });
50 | });
51 | }
52 |
53 | function getConnection(pool) {
54 | return new Promise((resolve, reject) => {
55 | pool.getConnection((e, conn) => {
56 | if (e) {
57 | reject(e);
58 | } else {
59 | resolve(conn);
60 | }
61 | });
62 | });
63 | }
64 |
65 | function commit(conn) {
66 | return new Promise((resolve, reject) => {
67 | conn.commit((e) => {
68 | if (e) {
69 | reject(e);
70 | } else {
71 | resolve();
72 | }
73 | });
74 | });
75 | }
76 |
77 | function rollback(conn) {
78 | return new Promise((resolve, reject) => {
79 | conn.rollback((e) => {
80 | if (e) {
81 | reject(e);
82 | } else {
83 | resolve();
84 | }
85 | });
86 | });
87 | }
88 |
89 | function forTransaction(conn, f) {
90 | return beginTransaction(conn).then(() => {
91 | return f(conn).then((data) => {
92 | return commit(conn).then(() => {
93 | return Promise.resolve(data);
94 | });
95 | }).catch((e) => {
96 | return rollback(conn).catch((e2) => {
97 | return Promise.reject([e, e2]);
98 | }).then(() => {
99 | return Promise.reject(e);
100 | });
101 | });
102 | });
103 | }
104 |
105 | function createEnv(host, user, pass, dbname) {
106 |
107 | var pool = mysql.createPool({
108 | host: host,
109 | user: user,
110 | password: pass,
111 | database: dbname,
112 | charset: 'utf8mb4'
113 | });
114 |
115 | function forConnection(f) {
116 | return getConnection(pool).then((conn) => {
117 | return f(conn).then((data) => {
118 | conn.release();
119 | return Promise.resolve(data);
120 | }).catch((e) => {
121 | conn.release();
122 | return Promise.reject(e);
123 | });
124 | });
125 | }
126 |
127 | // maybe logic is incorrect
128 | function forConnectionAndTransaction(f) {
129 | return forConnection((conn) => {
130 | return forTransaction(conn, f);
131 | });
132 | }
133 |
134 | function end() {
135 | return new Promise((resolve, reject) => {
136 | pool.end(e => {
137 | if (e) {
138 | reject(e);
139 | } else {
140 | resolve();
141 | }
142 | });
143 | });
144 | }
145 |
146 | return {
147 | forConnection: forConnection,
148 | forConnectionAndTransaction: forConnectionAndTransaction,
149 | end: end
150 | };
151 | }
152 |
153 | function escape(s) {
154 | return mysql.escape(s);
155 | }
156 |
157 | module.exports = {
158 | createEnv: createEnv,
159 | exec: exec,
160 | one: one,
161 | batch: batch,
162 | forTransaction: forTransaction,
163 | escape: escape
164 | };
165 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/ContextMenu.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.ContextMenu exposing (view)
2 |
3 | import Dict
4 | import Html exposing (..)
5 | import ContextMenu
6 | import Model.User as User
7 | import Model.Object as Object
8 | import Model.EditingFloor as EditingFloor
9 | import Model.I18n as I18n
10 | import Model.Floor as Floor
11 | import Page.Map.Msg exposing (..)
12 | import Page.Map.Model as Model exposing (Model, DraggingContext(..))
13 | import Page.Map.ContextMenuContext exposing (..)
14 |
15 |
16 | view : Model -> Html Msg
17 | view model =
18 | ContextMenu.view
19 | ContextMenu.defaultConfig
20 | ContextMenuMsg
21 | (toItemGroups model)
22 | model.contextMenu
23 |
24 |
25 | toItemGroups : Model -> ContextMenuContext -> List (List ( ContextMenu.Item, Msg ))
26 | toItemGroups model context =
27 | case context of
28 | ObjectContextMenu id ->
29 | let
30 | itemsForPerson =
31 | model.floor
32 | |> Maybe.andThen
33 | (\eFloor ->
34 | Floor.getObject id (EditingFloor.present eFloor)
35 | |> Maybe.andThen
36 | (\obj ->
37 | Object.relatedPerson obj
38 | |> Maybe.andThen
39 | (\personId ->
40 | Dict.get personId model.personInfo
41 | |> Maybe.map
42 | (\person ->
43 | [ ( ContextMenu.itemWithAnnotation (I18n.selectSamePost model.lang) person.post, SelectSamePost person.post )
44 | , ( ContextMenu.itemWithAnnotation (I18n.searchSamePost model.lang) person.post, SearchByPost person.post )
45 | ]
46 | )
47 | )
48 | )
49 | )
50 |
51 | forOneDesk =
52 | if [ id ] == model.selectedObjects then
53 | (Maybe.withDefault [] itemsForPerson)
54 | ++ [ ( ContextMenu.item (I18n.selectIsland model.lang), SelectIsland id )
55 | , ( ContextMenu.item (I18n.selectSameColor model.lang), SelectSameColor id )
56 | , ( ContextMenu.item (I18n.registerAsStamp model.lang), RegisterPrototype id )
57 | ]
58 | else
59 | []
60 |
61 | common =
62 | [ ( ContextMenu.item (I18n.pickupFirstWord model.lang), FirstNameOnly model.selectedObjects )
63 | , ( ContextMenu.item (I18n.removeSpaces model.lang), RemoveSpaces model.selectedObjects )
64 | , ( ContextMenu.item (I18n.rotate model.lang), RotateObjects model.selectedObjects )
65 | ]
66 |
67 | items =
68 | forOneDesk ++ common
69 | in
70 | [ items ]
71 |
72 | FloorInfoContextMenu floorId ->
73 | if Maybe.map (EditingFloor.present >> .id) model.floor == Just floorId then
74 | if User.isGuest model.user then
75 | []
76 | else if User.isAdmin model.user then
77 | [ [ ( ContextMenu.item (I18n.copyFloor model.lang), CopyFloor floorId False )
78 | , ( ContextMenu.item (I18n.copyAndCreateTemporaryFloor model.lang), CopyFloor floorId True )
79 | ]
80 | ]
81 | else
82 | [ [ ( ContextMenu.item (I18n.copyAndCreateTemporaryFloor model.lang), CopyFloor floorId True )
83 | ]
84 | ]
85 | else
86 | []
87 |
--------------------------------------------------------------------------------
/src/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
119 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/server/static/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
119 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/server/lib/profile-service.js:
--------------------------------------------------------------------------------
1 | var request = require('request');
2 | var log = require('./log.js');
3 |
4 | function send(token, method, url, data) {
5 | return new Promise((resolve, reject) => {
6 | var options = {
7 | method: method,
8 | url: url,
9 | headers: {
10 | 'Authorization': token ? 'JWT ' + token : ''
11 | },
12 | body: data,
13 | json: true
14 | };
15 | request(options, function(e, response, body) {
16 | if (e || response.statusCode >= 400) {
17 | log.system.error(response ? response.statusCode : e, 'profile service: failed ' + method + ' ' + url);
18 | body && body.message && log.system.error(body.message);
19 | if (response && response.statusCode === 401) {
20 | reject(401);
21 | } else {
22 | reject(body ? body.message : e || response.statusCode);
23 | }
24 | } else {
25 | log.system.debug(response.statusCode, 'profile service: success ' + method + ' ' + url);
26 | resolve(body);
27 | }
28 | });
29 | });
30 | }
31 |
32 | function get(token, url) {
33 | return send(token, 'GET', url, null);
34 | }
35 |
36 | function post(token, url, data) {
37 | return send(token, 'POST', url, data);
38 | }
39 |
40 | function put(token, url, data) {
41 | return send(token, 'PUT', url, data);
42 | }
43 |
44 | function fixPerson(profile) {
45 | return {
46 | id: profile.userId,
47 | tenantId: profile.tenantId,
48 | name: profile.name,
49 | empNo: profile.employeeId,
50 | post: profile.post || '', //TODO
51 | tel1: profile.extensionPhone,
52 | tel2: profile.cellPhone,
53 | mail: profile.mail,
54 | image: profile.picture || '' //TODO or default.png
55 | };
56 | }
57 |
58 | function getPerson(root, token, personId) {
59 | return get(token, root + '/1/profiles/' + personId).then((person) => {
60 | return Promise.resolve(fixPerson(person));
61 | }).catch((e) => {
62 | if (e === 404) {
63 | return Promise.resolve(null);
64 | }
65 | return Promise.reject(e);
66 | });
67 | }
68 |
69 | function getPeopleByIds(root, token, personIds) {
70 | return personIds.reduce((promise, personId) => {
71 | return promise.then(results => {
72 | return getPerson(root, token, personId).then(person => {
73 | if (person) {
74 | results.push(person);
75 | }
76 | return Promise.resolve(results);
77 | });
78 | })
79 | }, Promise.resolve([]));
80 | }
81 |
82 | function getPeopleByPost(root, token, post, exclusiveStartKey) {
83 | var url = root + '/1/profiles?q=' + encodeURIComponent(post) +
84 | (exclusiveStartKey ? '&exclusiveStartKey=' + exclusiveStartKey : '')
85 | return get(token, url).then((data) => {
86 | var people = data.profiles.map(fixPerson)
87 | if (data.lastEvaluatedKey) {
88 | return getPeopleByPost(root, token, post, data.lastEvaluatedKey).then((people2) => {
89 | return Promise.resolve(people.concat(people2));
90 | });
91 | } else {
92 | return Promise.resolve(people);
93 | }
94 | });
95 | }
96 |
97 | function savePerson(root, token, person) {
98 | person.userId = person.mail;
99 | person.employeeId = person.empNo;
100 | person.ruby = person.ruby || null;
101 | person.cellPhone = person.tel || null;
102 | person.extensionPhone = person.tel || null;
103 | person.picture = person.image || null;
104 | person.organization = person.organization || null; //TODO
105 | person.post = person.post || null;
106 | person.rank = '' || null;
107 | person.workspace = '' || null;
108 | return put(token, root + '/1/profiles/' + encodeURIComponent(person.userId), person);
109 | }
110 |
111 | function search(root, token, query, exclusiveStartKey) {
112 | var url = root + '/1/profiles?q=' + encodeURIComponent(query) +
113 | (exclusiveStartKey ? '&exclusiveStartKey=' + exclusiveStartKey : '');
114 | return get(token, url).then((data) => {
115 | var people = data.profiles.map(fixPerson);
116 | if (data.lastEvaluatedKey) {
117 | return search(root, token, query, data.lastEvaluatedKey).then((people2) => {
118 | return Promise.resolve(people.concat(people2));
119 | });
120 | } else {
121 | return Promise.resolve(people);
122 | }
123 | });
124 | }
125 |
126 | module.exports = {
127 | getPerson: getPerson,
128 | getPeopleByPost: getPeopleByPost,
129 | getPeopleByIds: getPeopleByIds,
130 | savePerson: savePerson,
131 | search: search
132 | };
133 |
--------------------------------------------------------------------------------
/src/elm/Model/ObjectsChange.elm:
--------------------------------------------------------------------------------
1 | module Model.ObjectsChange exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Model.Object as Object exposing (..)
5 | import CoreType exposing (..)
6 |
7 |
8 | type alias ObjectModification =
9 | { new : Object, old : Object, changes : List ObjectPropertyChange }
10 |
11 |
12 | type ObjectChange a
13 | = Added Object
14 | | Modified a
15 | | Deleted Object
16 |
17 |
18 | type alias ObjectsChange_ a =
19 | Dict ObjectId (ObjectChange a)
20 |
21 |
22 | type alias ObjectsChange =
23 | ObjectsChange_ Object
24 |
25 |
26 | type alias DetailedObjectsChange =
27 | ObjectsChange_ ObjectModification
28 |
29 |
30 | added : List Object -> ObjectsChange_ a
31 | added objects =
32 | objects
33 | |> List.map (\object -> ( Object.idOf object, Added object ))
34 | |> Dict.fromList
35 |
36 |
37 | modified : List ( ObjectId, a ) -> ObjectsChange_ a
38 | modified idRelatedList =
39 | idRelatedList
40 | |> List.map (\( id, a ) -> ( id, Modified a ))
41 | |> Dict.fromList
42 |
43 |
44 | empty : ObjectsChange
45 | empty =
46 | Dict.empty
47 |
48 |
49 | isEmpty : ObjectsChange_ a -> Bool
50 | isEmpty change =
51 | Dict.isEmpty change
52 |
53 |
54 | merge : Dict ObjectId Object -> ObjectsChange -> ObjectsChange -> ObjectsChange
55 | merge currentObjects new old =
56 | Dict.merge
57 | (\id new dict -> insertToMergedDict currentObjects id new dict)
58 | (\id new old dict ->
59 | case ( new, old ) of
60 | ( Deleted _, Added _ ) ->
61 | dict
62 |
63 | ( Modified object, Added _ ) ->
64 | insertToMergedDict currentObjects id (Added object) dict
65 |
66 | _ ->
67 | insertToMergedDict currentObjects id new dict
68 | )
69 | (\id old dict -> insertToMergedDict currentObjects id old dict)
70 | new
71 | old
72 | Dict.empty
73 |
74 |
75 | insertToMergedDict : Dict ObjectId Object -> ObjectId -> ObjectChange Object -> ObjectsChange -> ObjectsChange
76 | insertToMergedDict currentObjects id value dict =
77 | currentObjects
78 | |> Dict.get id
79 | |> Maybe.map
80 | (\currentObject ->
81 | Dict.insert id (copyCurrentUpdateAtToObjects currentObject value) dict
82 | )
83 | |> Maybe.withDefault (Dict.insert id value dict)
84 |
85 |
86 |
87 | -- current object does not exist if deleted
88 |
89 |
90 | copyCurrentUpdateAtToObjects : Object -> ObjectChange Object -> ObjectChange Object
91 | copyCurrentUpdateAtToObjects currentObject modification =
92 | case modification of
93 | Added object ->
94 | Added (Object.copyUpdateAt currentObject object)
95 |
96 | Modified object ->
97 | Modified (Object.copyUpdateAt currentObject object)
98 |
99 | Deleted object ->
100 | Deleted (Object.copyUpdateAt currentObject object)
101 |
102 |
103 | fromList : List ( ObjectId, ObjectChange a ) -> ObjectsChange_ a
104 | fromList list =
105 | Dict.fromList list
106 |
107 |
108 | toList : ObjectsChange_ a -> List (ObjectChange a)
109 | toList change =
110 | List.map Tuple.second (Dict.toList change)
111 |
112 |
113 | separate : ObjectsChange_ a -> { added : List Object, modified : List a, deleted : List Object }
114 | separate change =
115 | let
116 | ( added, modified, deleted ) =
117 | Dict.foldl
118 | (\_ value ( added, modified, deleted ) ->
119 | case value of
120 | Added object ->
121 | ( object :: added, modified, deleted )
122 |
123 | Modified a ->
124 | ( added, a :: modified, deleted )
125 |
126 | Deleted object ->
127 | ( added, modified, object :: deleted )
128 | )
129 | ( [], [], [] )
130 | change
131 | in
132 | { added = added
133 | , modified = modified
134 | , deleted = deleted
135 | }
136 |
137 |
138 | simplify : DetailedObjectsChange -> ObjectsChange
139 | simplify change =
140 | change
141 | |> Dict.map
142 | (\id value ->
143 | case value of
144 | Added object ->
145 | Added object
146 |
147 | Modified mod ->
148 | Modified mod.new
149 |
150 | Deleted object ->
151 | Deleted object
152 | )
153 |
--------------------------------------------------------------------------------
/src/elm/View/CommonStyles.elm:
--------------------------------------------------------------------------------
1 | module View.CommonStyles exposing (..)
2 |
3 | import Util.StyleUtil exposing (..)
4 | import CoreType exposing (..)
5 |
6 |
7 | type alias S =
8 | List ( String, String )
9 |
10 |
11 | invertedTextColor : String
12 | invertedTextColor =
13 | "#fff"
14 |
15 |
16 | inputTextColor : String
17 | inputTextColor =
18 | "#333"
19 |
20 |
21 | selectColor : String
22 | selectColor =
23 | "#69e"
24 |
25 |
26 | errorTextColor : String
27 | errorTextColor =
28 | "#d45"
29 |
30 |
31 | hoverBackgroundColor : String
32 | hoverBackgroundColor =
33 | "#9ce"
34 |
35 |
36 | noMargin : S
37 | noMargin =
38 | [ ( "margin", "0" ) ]
39 |
40 |
41 | noPadding : S
42 | noPadding =
43 | [ ( "padding", "0" ) ]
44 |
45 |
46 | noUserSelect : S
47 | noUserSelect =
48 | [ ( "user-select", "none" )
49 | , ( "MozUserSelect", "none" ) -- must be UpperCamelCase
50 | , ( "-ms-user-select", "none" )
51 | , ( "-webkit-user-select", "none" )
52 | ]
53 |
54 |
55 | flex : S
56 | flex =
57 | [ ( "display", "flex" ) ]
58 |
59 |
60 | rect : Position -> Size -> S
61 | rect { x, y } { width, height } =
62 | [ ( "top", px y )
63 | , ( "left", px x )
64 | , ( "width", px width )
65 | , ( "height", px height )
66 | ]
67 |
68 |
69 | absoluteRect : Position -> Size -> S
70 | absoluteRect pos size =
71 | ( "position", "absolute" ) :: (rect pos size)
72 |
73 |
74 | card : S
75 | card =
76 | [ ( "padding", "20px" )
77 | , ( "box-shadow", "rgba(0, 0, 0, 0.25) 0px 6px 4px" )
78 | , ( "border-bottom", "solid 1px #ccc" )
79 | ]
80 |
81 |
82 | formControl : S
83 | formControl =
84 | [ ( "margin-bottom", "6px" )
85 | ]
86 |
87 |
88 | button : S
89 | button =
90 | [ ( "display", "block" )
91 | , ( "text-align", "center" )
92 | , ( "width", "100%" )
93 | , ( "padding", "6px 12px" )
94 | , ( "box-sizing", "border-box" )
95 | , ( "font-size", "13px" )
96 | , ( "cursor", "pointer" )
97 | ]
98 |
99 |
100 | defaultButton : S
101 | defaultButton =
102 | button
103 | ++ [ ( "background-color", "#eee" )
104 | , ( "border", "solid 1px #aaa" )
105 | ]
106 |
107 |
108 | primaryButton : S
109 | primaryButton =
110 | button
111 | ++ [ ( "background-color", "rgb(100, 180, 85)" )
112 | , ( "color", invertedTextColor )
113 | , ( "border", "solid 1px rgb(100, 180, 85)" )
114 | ]
115 |
116 |
117 | dangerButton : S
118 | dangerButton =
119 | button
120 | ++ [ ( "background-color", "rgb(221, 116, 116)" )
121 | , ( "color", invertedTextColor )
122 | , ( "border", "solid 1px rgb(221, 116, 116)" )
123 | ]
124 |
125 |
126 | input : S
127 | input =
128 | [ ( "color", inputTextColor )
129 | , ( "width", "100%" )
130 | , ( "background-color", "#fff" )
131 | , ( "box-shadow", "inset 0 1px 2px rgba(0,0,0,0.075)" )
132 | , ( "border", "1px solid #ddd" )
133 | , ( "padding", "6px 12px" )
134 | , ( "box-sizing", "border-box" )
135 | , ( "font-size", "13px" )
136 | ]
137 |
138 |
139 | popup : Int -> String -> S
140 | popup padding position =
141 | [ ( "box-sizing", "border-box" )
142 | , ( "padding", px padding )
143 | , ( "background-color", "#fff" )
144 | , ( "position", position )
145 | ]
146 |
147 |
148 | modalBackground : Int -> S
149 | modalBackground zIndex =
150 | [ ( "background-color", "rgba(0,0,0,0.5)" )
151 | , ( "position", "fixed" )
152 | , ( "left", "0" )
153 | , ( "right", "0" )
154 | , ( "top", "0" )
155 | , ( "bottom", "0" )
156 | , ( "z-index", toString zIndex )
157 | ]
158 |
159 |
160 | dialogWithSize : Int -> Int -> Int -> S
161 | dialogWithSize zIndex width height =
162 | popup 20 "fixed"
163 | ++ [ ( "margin", "auto" )
164 | , ( "left", "0" )
165 | , ( "right", "0" )
166 | , ( "top", "0" )
167 | , ( "bottom", "0" )
168 | , ( "width", px width )
169 | , ( "height", px height )
170 | , ( "z-index", toString zIndex )
171 | ]
172 |
173 |
174 | dialogWithMarginParcentage : Int -> Int -> Int -> S
175 | dialogWithMarginParcentage zIndex top left =
176 | popup 20 "fixed"
177 | ++ [ ( "left", percent left )
178 | , ( "right", percent left )
179 | , ( "top", percent top )
180 | , ( "bottom", percent top )
181 | , ( "z-index", toString zIndex )
182 | ]
183 |
184 |
185 | dialogFooter : S
186 | dialogFooter =
187 | [ ( "position", "absolute" )
188 | , ( "left", "0" )
189 | , ( "bottom", "0" )
190 | , ( "display", "flex" )
191 | , ( "padding", px 20 )
192 | , ( "width", "100%" )
193 | , ( "box-sizing", "border-box" )
194 | ]
195 |
--------------------------------------------------------------------------------
/src/elm/Page/Master/PrototypeForm.elm:
--------------------------------------------------------------------------------
1 | module Page.Master.PrototypeForm exposing (..)
2 |
3 | import Model.Object exposing (Shape)
4 | import Model.Prototype exposing (Prototype)
5 |
6 |
7 | type alias PrototypeId =
8 | String
9 |
10 |
11 | type alias PrototypeForm =
12 | { id : PrototypeId
13 | , name : String
14 | , color : String
15 | , backgroundColor : String
16 | , width : String
17 | , height : String
18 | , fontSize : String
19 | , shape : Shape
20 | , personId : Maybe String
21 | }
22 |
23 |
24 | fromPrototype : Prototype -> PrototypeForm
25 | fromPrototype prototype =
26 | { id = prototype.id
27 | , name = prototype.name
28 | , color = prototype.color
29 | , backgroundColor = prototype.backgroundColor
30 | , width = toString prototype.width
31 | , height = toString prototype.height
32 | , fontSize = toString prototype.fontSize
33 | , shape = prototype.shape
34 | , personId = prototype.personId
35 | }
36 |
37 |
38 | toPrototype : PrototypeForm -> Result String Prototype
39 | toPrototype form =
40 | validateName form.name
41 | |> Result.andThen
42 | (\name ->
43 | validateColor form.color
44 | |> Result.andThen
45 | (\color ->
46 | validateBackgroundColor form.backgroundColor
47 | |> Result.andThen
48 | (\backgroundColor ->
49 | validateWidth form.width
50 | |> Result.andThen
51 | (\width ->
52 | validateHeight form.height
53 | |> Result.andThen
54 | (\height ->
55 | validateFontSize form.fontSize
56 | |> Result.map
57 | (\fontSize ->
58 | { id = form.id
59 | , name = name
60 | , color = color
61 | , backgroundColor = backgroundColor
62 | , width = width
63 | , height = height
64 | , fontSize = fontSize
65 | , shape = form.shape
66 | , personId = form.personId
67 | }
68 | )
69 | )
70 | )
71 | )
72 | )
73 | )
74 |
75 |
76 | validateName : String -> Result String String
77 | validateName name =
78 | Ok name
79 |
80 |
81 | validateColor : String -> Result String String
82 | validateColor color =
83 | Ok color
84 |
85 |
86 | validateBackgroundColor : String -> Result String String
87 | validateBackgroundColor backgroundColor =
88 | Ok backgroundColor
89 |
90 |
91 | validateFontSize : String -> Result String Float
92 | validateFontSize size =
93 | case String.toFloat size of
94 | Ok i ->
95 | if i >= 10 then
96 | Ok i
97 | else
98 | Err ("font size must not be less than 10")
99 |
100 | Err _ ->
101 | Err ("font size must be a number")
102 |
103 |
104 | validateWidth : String -> Result String Int
105 | validateWidth width =
106 | validateLength "width" width
107 |
108 |
109 | validateHeight : String -> Result String Int
110 | validateHeight height =
111 | validateLength "height" height
112 |
113 |
114 | validateLength : String -> String -> Result String Int
115 | validateLength fieldName length =
116 | case String.toInt length of
117 | Ok i ->
118 | if i > 0 && i % 8 == 0 then
119 | Ok i
120 | else
121 | Err (fieldName ++ " must be multiple of 8")
122 |
123 | Err _ ->
124 | Err (fieldName ++ " must be multiple of 8")
125 |
--------------------------------------------------------------------------------
/src/elm/View/Theme.elm:
--------------------------------------------------------------------------------
1 | module View.Theme exposing (..)
2 |
3 | -- GLOBAL LIGHT THEME
4 |
5 |
6 | hoverBackgroundColor =
7 | "#9ce"
8 |
9 |
10 | normalTextColor =
11 | "#000"
12 |
13 |
14 |
15 | -- GLOBAL DARK THEME
16 |
17 |
18 | invertedText =
19 | "#fff"
20 |
21 |
22 |
23 | -- SELECT / HOVER
24 |
25 |
26 | selectColor =
27 | "#69e"
28 |
29 |
30 |
31 | -- ERROR
32 |
33 |
34 | errorTextColor =
35 | "#d45"
36 |
37 |
38 |
39 | -- HEADER
40 |
41 |
42 | headerBgColor =
43 | "rgb(100,100,120)"
44 |
45 |
46 | headerTextColor =
47 | "#eee"
48 |
49 |
50 | greetingImageBorder =
51 | "#888"
52 |
53 |
54 | userMenuBgColor =
55 | "rgb(100, 100, 120)"
56 |
57 |
58 | userMenuBoxShadow =
59 | "rgba(0, 0, 0, 0.2) 0px 3px 4px inset"
60 |
61 |
62 |
63 | -- STATUS BAR
64 |
65 |
66 | successBarBgColor =
67 | "#4c5"
68 |
69 |
70 | errorBarBgColor =
71 | "#d45"
72 |
73 |
74 |
75 | -- POPUP / Contextmenu
76 |
77 |
78 | popupBgColor =
79 | "#fff"
80 |
81 |
82 | modalBackgroundBgColor =
83 | "rgba(0,0,0,0.5)"
84 |
85 |
86 |
87 | -- BUTTON
88 |
89 |
90 | defaultButtonBgColor =
91 | "#eee"
92 |
93 |
94 | defaultButtonBorder =
95 | "#aaa"
96 |
97 |
98 | primaryButtonBgColor =
99 | "rgb(100, 180, 85)"
100 |
101 |
102 | primaryButtonBorder =
103 | "rgb(100, 180, 85)"
104 |
105 |
106 |
107 | -- OBJECT
108 |
109 |
110 | deskObjectBorderLighter =
111 | "rgba(100,100,100,0.3)"
112 |
113 |
114 | deskBorderDarker =
115 | "rgba(100,100,100,0.7)"
116 |
117 |
118 | labelObjectBgColorVisibleTransparent =
119 | "rgba(255,255,255,0.2)"
120 |
121 |
122 | labelObjectBorder =
123 | "rgba(100,100,100,0.3)"
124 |
125 |
126 | personMatchedBgColor =
127 | "#6a6"
128 |
129 |
130 | personNotMatchedBgColor =
131 | "#ccc"
132 |
133 |
134 |
135 | -- RELATED PERSON / CANDIDATE
136 |
137 |
138 | unsetRelatedPersonButtonBgColor =
139 | "#fff"
140 |
141 |
142 | unsetRelatedPersonButtonBorder =
143 | "#ddd"
144 |
145 |
146 | unsetRelatedPersonButtonHoverBgColor =
147 | "#a66"
148 |
149 |
150 | candidatesViewRelatedPersonBgColor =
151 | "#fff"
152 |
153 |
154 | candidatesViewRelatedPersonBorder =
155 | "#ddd"
156 |
157 |
158 | candidateItemBorderBottom =
159 | "#ddd"
160 |
161 |
162 | candidateItemBgColorSelected =
163 | hoverBackgroundColor
164 |
165 |
166 | candidateItemBgColor =
167 | "#fff"
168 |
169 |
170 | candidateItemHoverBgColor =
171 | hoverBackgroundColor
172 |
173 |
174 |
175 | -- CONTEXT MENU
176 |
177 |
178 | contextMenuBgColor =
179 | "#fff"
180 |
181 |
182 | contextMenuBorder =
183 | "#eee"
184 |
185 |
186 | contextMenuItemHover =
187 | "#9ce"
188 |
189 |
190 |
191 | -- CANVAS
192 |
193 |
194 | canvasViewBgColor =
195 | "#fff"
196 |
197 |
198 | canvasViewForPrintBgColor =
199 | "#fff"
200 |
201 |
202 | canvasContainerBgColor =
203 | "#fff"
204 |
205 |
206 | canvasContainerBgColorPrintMode =
207 | "#000"
208 |
209 |
210 |
211 | -- SUBVIEW
212 |
213 |
214 | subViewBackground =
215 | "#eee"
216 |
217 |
218 | subViewTabBgColorActive =
219 | "#eee"
220 |
221 |
222 | subViewTabBgColor =
223 | "#eee"
224 |
225 |
226 | subViewTabBoxShadow =
227 | "inset -4px 0 4px rgba(0,0,0,0.03)"
228 |
229 |
230 |
231 | -- MODE SELECTOR
232 |
233 |
234 | modeSelectionViewEachBorder =
235 | "#666"
236 |
237 |
238 | modeSelectionViewEachBgColor =
239 | selectColor
240 |
241 |
242 | modeSelectionViewEachTextColor =
243 | "#fff"
244 |
245 |
246 |
247 | -- PROPERTIES
248 |
249 |
250 | colorPropertyBorder =
251 | "#666"
252 |
253 |
254 | shapePropertyBorder =
255 | "#666"
256 |
257 |
258 |
259 | -- PROTOTYPE PREVIEW
260 |
261 |
262 | prototypePreviewViewBorderSelected =
263 | selectColor
264 |
265 |
266 | prototypePreviewViewBorder =
267 | "#666"
268 |
269 |
270 | prototypePreviewViewBgColor =
271 | "#fff"
272 |
273 |
274 | prototypePreviewScrollBgColor =
275 | "#ccc"
276 |
277 |
278 |
279 | -- TEXT INPUT
280 |
281 |
282 | inputTextColor =
283 | "#333"
284 |
285 |
286 | inputBgColor =
287 | "#fff"
288 |
289 |
290 | inputBorder =
291 | "#ddd"
292 |
293 |
294 | inputBoxShadow =
295 | "inset 0 1px 2px rgba(0,0,0,0.075)"
296 |
297 |
298 |
299 | -- FLOORS INFO
300 |
301 |
302 | floorsInfoViewItemBgColorPrivate =
303 | "#dbdbdb"
304 |
305 |
306 | floorsInfoViewItemBgColor =
307 | "#fff"
308 |
309 |
310 | floorsInfoViewItemBorderSelected =
311 | selectColor
312 |
313 |
314 | floorsInfoViewItemBorder =
315 | "#d0d0d0"
316 |
317 |
318 | floorsInfoViewItemHoverBgColorPrivate =
319 | "#ddd"
320 |
321 |
322 | floorsInfoViewItemHoverBgColor =
323 | "#eee"
324 |
325 |
326 | floorPropertyTextBorderBottom =
327 | "#aaa"
328 |
329 |
330 | loginContainerBorder =
331 | "#aaa"
332 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/SearchResultItemView.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.SearchResultItemView exposing (Item(..), view)
2 |
3 | import Time exposing (Time)
4 | import Html exposing (..)
5 | import Html.Attributes exposing (..)
6 | import Html.Events exposing (..)
7 | import CoreType exposing (..)
8 | import Model.I18n as I18n exposing (Language)
9 | import View.Icons as Icons
10 | import View.Styles as S
11 |
12 |
13 | type alias PostName =
14 | String
15 |
16 |
17 | type alias ObjectName =
18 | String
19 |
20 |
21 | type alias PersonName =
22 | String
23 |
24 |
25 | type alias FloorName =
26 | String
27 |
28 |
29 |
30 | -- VIEW MODEL
31 |
32 |
33 | type Item
34 | = Post PostName
35 | | Object ObjectId ObjectName FloorId FloorName (Maybe ( PersonId, PersonName )) Time Bool
36 | | MissingPerson PersonId PersonName
37 |
38 |
39 | view : Maybe FloorId -> msg -> Maybe (PersonId -> PersonName -> msg) -> Maybe (ObjectId -> String -> Maybe PersonId -> FloorId -> Time -> msg) -> Language -> Item -> Html msg
40 | view currentFloorId onSelect onStartDragMissing onStartDragExisting lang item =
41 | case item of
42 | Post postName ->
43 | wrapForNonDrag <|
44 | itemViewCommon postIcon <|
45 | div [] [ itemViewLabel (Just onSelect) False postName ]
46 |
47 | Object objectId _ floorId floorName (Just ( personId, personName )) updateAt focused ->
48 | let
49 | wrap =
50 | case onStartDragExisting of
51 | Just onStartDragExisting ->
52 | if currentFloorId == Just floorId then
53 | identity
54 | else
55 | wrapForDrag (onStartDragExisting objectId personName (Just personId) floorId updateAt)
56 |
57 | Nothing ->
58 | identity
59 | in
60 | wrap <|
61 | itemViewCommon personIcon <|
62 | div [] [ itemViewLabel (Just onSelect) focused (personName ++ "(" ++ floorName ++ ")") ]
63 |
64 | Object objectId objectName floorId floorName Nothing updateAt focused ->
65 | let
66 | wrap =
67 | case onStartDragExisting of
68 | Just onStartDragExisting ->
69 | wrapForDrag (onStartDragExisting objectId objectName Nothing floorId updateAt)
70 |
71 | Nothing ->
72 | identity
73 | in
74 | wrap <|
75 | itemViewCommon noIcon <|
76 | div [] [ itemViewLabel (Just onSelect) focused (objectName ++ "(" ++ floorName ++ ")") ]
77 |
78 | MissingPerson personId personName ->
79 | let
80 | wrap =
81 | case onStartDragMissing of
82 | Just onStartDragMissing ->
83 | wrapForDrag (onStartDragMissing personId personName)
84 |
85 | Nothing ->
86 | identity
87 | in
88 | wrap <|
89 | itemViewCommon personIcon <|
90 | div [] [ itemViewLabel Nothing False (personName ++ "(" ++ I18n.missing lang ++ ")") ]
91 |
92 |
93 | wrapForNonDrag : Html msg -> Html msg
94 | wrapForNonDrag child =
95 | div
96 | [ style (S.searchResultItem False)
97 | ]
98 | [ child ]
99 |
100 |
101 | wrapForDrag : msg -> Html msg -> Html msg
102 | wrapForDrag onStartDrag child =
103 | div
104 | [ onMouseDown onStartDrag
105 | , style (S.searchResultItem True)
106 | ]
107 | [ child ]
108 |
109 |
110 | itemViewCommon : Html msg -> Html msg -> Html msg
111 | itemViewCommon icon label =
112 | div
113 | [ style S.searchResultItemInner ]
114 | [ icon, label ]
115 |
116 |
117 | itemViewLabel : Maybe msg -> Bool -> String -> Html msg
118 | itemViewLabel onSelect focused s =
119 | let
120 | selectable =
121 | onSelect /= Nothing
122 |
123 | events =
124 | case onSelect of
125 | Just onSelect ->
126 | [ onClick onSelect ]
127 |
128 | Nothing ->
129 | []
130 | in
131 | span
132 | (events ++ [ style (S.searchResultItemInnerLabel selectable focused) ])
133 | [ text s ]
134 |
135 |
136 | personIcon : Html msg
137 | personIcon =
138 | div [ style S.searchResultItemIcon ] [ Icons.searchResultItemPerson ]
139 |
140 |
141 | postIcon : Html msg
142 | postIcon =
143 | div [ style S.searchResultItemIcon ] [ Icons.searchResultItemPost ]
144 |
145 |
146 | noIcon : Html msg
147 | noIcon =
148 | div [ style S.searchResultItemIcon ] []
149 |
--------------------------------------------------------------------------------
/src/elm/Page/Login/Main.elm:
--------------------------------------------------------------------------------
1 | port module Page.Login.Main exposing (..)
2 |
3 | import Html exposing (Html, text, div, input, form, h2)
4 | import Html.Attributes exposing (type_, value, action, method, style, autofocus)
5 | import Task
6 | import Http
7 | import Navigation
8 | import API.API as API
9 | import API.Page as Page
10 | import View.HeaderView as HeaderView
11 | import Util.HtmlUtil as HtmlUtil exposing (..)
12 | import Model.I18n as I18n exposing (Language(..))
13 | import Page.Login.Styles as Styles
14 |
15 |
16 | port saveToken : String -> Cmd msg
17 |
18 |
19 | port tokenSaved : ({} -> msg) -> Sub msg
20 |
21 |
22 | main : Program Flags Model Msg
23 | main =
24 | Html.programWithFlags
25 | { init = init
26 | , view = view
27 | , update = update
28 | , subscriptions = \_ -> tokenSaved (always TokenSaved)
29 | }
30 |
31 |
32 |
33 | --------
34 |
35 |
36 | type alias Model =
37 | { accountServiceRoot : String
38 | , title : String
39 | , error : Maybe String
40 | , inputId : String
41 | , inputPass : String
42 | , lang : Language
43 | }
44 |
45 |
46 |
47 | ----
48 |
49 |
50 | type alias Flags =
51 | { accountServiceRoot : String
52 | , title : String
53 | , lang : String
54 | }
55 |
56 |
57 | type Msg
58 | = InputId String
59 | | InputPass String
60 | | Submit
61 | | Error Http.Error
62 | | Success String
63 | | TokenSaved
64 |
65 |
66 | init : Flags -> ( Model, Cmd Msg )
67 | init { accountServiceRoot, title, lang } =
68 | { accountServiceRoot = accountServiceRoot
69 | , title = title
70 | , error = Nothing
71 | , inputId = ""
72 | , inputPass = ""
73 | , lang =
74 | if lang == "ja" then
75 | JA
76 | else
77 | EN
78 | }
79 | ! []
80 |
81 |
82 | update : Msg -> Model -> ( Model, Cmd Msg )
83 | update message model =
84 | case message of
85 | InputId s ->
86 | { model | inputId = s } ! []
87 |
88 | InputPass s ->
89 | { model | inputPass = s } ! []
90 |
91 | Submit ->
92 | let
93 | cmd =
94 | API.login
95 | model.accountServiceRoot
96 | model.inputId
97 | model.inputPass
98 | |> Task.map Success
99 | |> Task.onError (Error >> Task.succeed)
100 | |> Task.perform identity
101 | in
102 | model ! [ cmd ]
103 |
104 | Error e ->
105 | let
106 | _ =
107 | Debug.log "Error" e
108 |
109 | message =
110 | case e of
111 | Http.NetworkError ->
112 | -- "network error"
113 | "unauthorized"
114 |
115 | _ ->
116 | "unauthorized"
117 | in
118 | { model | error = Just message } ! []
119 |
120 | Success token ->
121 | model ! [ saveToken token ]
122 |
123 | TokenSaved ->
124 | model ! [ Navigation.load Page.top ]
125 |
126 |
127 |
128 | ----
129 |
130 |
131 | view : Model -> Html Msg
132 | view model =
133 | div
134 | []
135 | [ HeaderView.view False model.title (Just ".") (text "")
136 | , container model
137 | ]
138 |
139 |
140 | container : Model -> Html Msg
141 | container model =
142 | div
143 | [ style Styles.loginContainer ]
144 | [ h2 [ style Styles.loginCaption ] [ text (I18n.signInTo model.lang model.title) ]
145 | , div [ style Styles.loginError ] [ text (Maybe.withDefault "" model.error) ]
146 | , loginForm model
147 | ]
148 |
149 |
150 | loginForm : Model -> Html Msg
151 | loginForm model =
152 | HtmlUtil.form_ Submit
153 | []
154 | [ div
155 | []
156 | [ div [] [ text (I18n.mailAddress model.lang) ]
157 | , input
158 | [ style Styles.formInput
159 | , onInput InputId
160 | , type_ "text"
161 | , value model.inputId
162 | , autofocus True
163 | ]
164 | []
165 | ]
166 | , div
167 | []
168 | [ div [] [ text (I18n.password model.lang) ]
169 | , input
170 | [ style Styles.formInput
171 | , onInput InputPass
172 | , type_ "password"
173 | , value model.inputPass
174 | ]
175 | []
176 | ]
177 | , input
178 | [ style Styles.loginSubmitButton
179 | , type_ "submit"
180 | , value (I18n.signIn model.lang)
181 | ]
182 | []
183 | ]
184 |
--------------------------------------------------------------------------------
/src/elm/Util/HttpUtil.elm:
--------------------------------------------------------------------------------
1 | module Util.HttpUtil exposing (..)
2 |
3 | import Task exposing (..)
4 | import Native.HttpUtil
5 | import Util.File as File exposing (File(File))
6 | import Http exposing (..)
7 | import Json.Decode as D exposing (Decoder)
8 | import Json.Encode as E
9 |
10 |
11 | encodeHeaders : List ( String, String ) -> E.Value
12 | encodeHeaders headers =
13 | E.list (List.map (\( k, v ) -> E.list [ E.string k, E.string v ]) headers)
14 |
15 |
16 | sendFile : String -> String -> List ( String, String ) -> File.File -> Task a ()
17 | sendFile method url headers (File file) =
18 | Native.HttpUtil.sendFile method url (encodeHeaders headers) file
19 |
20 |
21 | makeUrl : String -> List ( String, String ) -> String
22 | makeUrl baseUrl args =
23 | case args of
24 | [] ->
25 | baseUrl
26 |
27 | _ ->
28 | baseUrl ++ "?" ++ String.join "&" (List.map queryPair args)
29 |
30 |
31 | queryPair : ( String, String ) -> String
32 | queryPair ( key, value ) =
33 | queryEscape key ++ "=" ++ queryEscape value
34 |
35 |
36 | queryEscape : String -> String
37 | queryEscape string =
38 | String.join "+" (String.split "%20" (Http.encodeUri string))
39 |
40 |
41 | authorization : String -> Header
42 | authorization s =
43 | Http.header "Authorization" s
44 |
45 |
46 | authorizationTuple : String -> ( String, String )
47 | authorizationTuple s =
48 | ( "Authorization", s )
49 |
50 |
51 | sendJson : String -> Decoder value -> String -> List Header -> Http.Body -> Task Http.Error value
52 | sendJson method decoder url headers body =
53 | { method = method
54 | , headers = headers
55 | , url = url
56 | , body = body
57 | , expect = Http.expectJson decoder
58 | , timeout = Nothing
59 | , withCredentials = True
60 | }
61 | |> Http.request
62 | |> Http.toTask
63 |
64 |
65 | sendJsonNoResponse : String -> String -> List Header -> Http.Body -> Task Http.Error ()
66 | sendJsonNoResponse method url headers body =
67 | { method = method
68 | , headers = headers
69 | , url = url
70 | , body = body
71 | , expect = Http.expectStringResponse (\_ -> Ok ())
72 | , timeout = Nothing
73 | , withCredentials = True
74 | }
75 | |> Http.request
76 | |> Http.toTask
77 |
78 |
79 | get : Decoder value -> String -> List Header -> Task Http.Error value
80 | get decoder url headers =
81 | { method = "GET"
82 | , headers = headers
83 | , url = url
84 | , body = Http.emptyBody
85 | , expect = Http.expectJson decoder
86 | , timeout = Nothing
87 | , withCredentials = True
88 | }
89 | |> Http.request
90 | |> Http.toTask
91 |
92 |
93 | getWithoutCache : Decoder value -> String -> List Header -> Task Http.Error value
94 | getWithoutCache decoder url headers =
95 | let
96 | headers_ =
97 | [ Http.header "Pragma" "no-cache"
98 | , Http.header "Cache-Control" "no-cache"
99 | , Http.header "If-Modified-Since" "Thu, 01 Jun 1970 00:00:00 GMT"
100 | ]
101 | ++ headers
102 | in
103 | get decoder url headers_
104 |
105 |
106 | postJson : Decoder value -> String -> List Header -> Http.Body -> Task Http.Error value
107 | postJson =
108 | sendJson "POST"
109 |
110 |
111 | postJsonNoResponse : String -> List Header -> Http.Body -> Task Http.Error ()
112 | postJsonNoResponse =
113 | sendJsonNoResponse "POST"
114 |
115 |
116 | putJson : Decoder value -> String -> List Header -> Http.Body -> Task Http.Error value
117 | putJson =
118 | sendJson "PUT"
119 |
120 |
121 | putJsonNoResponse : String -> List Header -> Http.Body -> Task Http.Error ()
122 | putJsonNoResponse =
123 | sendJsonNoResponse "PUT"
124 |
125 |
126 | patchJson : Decoder value -> String -> List Header -> Http.Body -> Task Http.Error value
127 | patchJson =
128 | sendJson "PATCH"
129 |
130 |
131 | patchJsonNoResponse : String -> List Header -> Http.Body -> Task Http.Error ()
132 | patchJsonNoResponse =
133 | sendJsonNoResponse "PATCH"
134 |
135 |
136 | deleteJson : Decoder value -> String -> List Header -> Http.Body -> Task Http.Error value
137 | deleteJson =
138 | sendJson "DELETE"
139 |
140 |
141 | deleteJsonNoResponse : String -> List Header -> Http.Body -> Task Http.Error ()
142 | deleteJsonNoResponse =
143 | sendJsonNoResponse "DELETE"
144 |
145 |
146 | recover404 : Task Http.Error a -> Task Http.Error (Maybe a)
147 | recover404 task =
148 | task
149 | |> Task.map Just
150 | |> Task.onError
151 | (\e ->
152 | case e of
153 | Http.BadStatus res ->
154 | if res.status.code == 404 then
155 | Task.succeed Nothing
156 | else
157 | Task.fail e
158 |
159 | e ->
160 | Task.fail e
161 | )
162 |
--------------------------------------------------------------------------------
/src/elm/Model/EditingFloor.elm:
--------------------------------------------------------------------------------
1 | module Model.EditingFloor exposing (..)
2 |
3 | import Model.Floor as Floor exposing (Floor)
4 | import Model.FloorDiff as FloorDiff
5 | import Model.ObjectsChange as ObjectsChange exposing (ObjectsChange)
6 | import Util.UndoList as UndoList exposing (UndoList)
7 |
8 |
9 | type alias EditingFloor =
10 | { undoList : UndoList Floor
11 | }
12 |
13 |
14 | init : Floor -> EditingFloor
15 | init floor =
16 | { undoList = UndoList.init floor
17 | }
18 |
19 |
20 | updateFloorAndObjects : (Floor -> Floor) -> EditingFloor -> ( EditingFloor, Floor, ObjectsChange )
21 | updateFloorAndObjects f efloor =
22 | let
23 | floor =
24 | efloor.undoList.present
25 |
26 | newFloor =
27 | f floor
28 |
29 | propChanged =
30 | FloorDiff.diffPropertyChanges newFloor (Just floor)
31 |
32 | objectsChange =
33 | FloorDiff.diffObjects newFloor.objects floor.objects
34 | |> ObjectsChange.simplify
35 |
36 | changed =
37 | propChanged /= [] || (not <| ObjectsChange.isEmpty objectsChange)
38 |
39 | newUndoList =
40 | if changed then
41 | UndoList.new newFloor efloor.undoList
42 | else
43 | efloor.undoList
44 | in
45 | ( { efloor | undoList = newUndoList }
46 | , newFloor
47 | , objectsChange
48 | )
49 |
50 |
51 | updateFloor : (Floor -> Floor) -> EditingFloor -> ( EditingFloor, Floor )
52 | updateFloor f efloor =
53 | let
54 | floor =
55 | efloor.undoList.present
56 |
57 | newFloor =
58 | f floor
59 |
60 | propChanged =
61 | FloorDiff.diffPropertyChanges newFloor (Just floor)
62 |
63 | changed =
64 | propChanged /= []
65 |
66 | newUndoList =
67 | if changed then
68 | UndoList.new newFloor efloor.undoList
69 | else
70 | efloor.undoList
71 | in
72 | ( { efloor | undoList = newUndoList }
73 | , newFloor
74 | )
75 |
76 |
77 | updateObjects : (Floor -> Floor) -> EditingFloor -> ( EditingFloor, ObjectsChange )
78 | updateObjects f efloor =
79 | let
80 | floor =
81 | efloor.undoList.present
82 |
83 | newFloor =
84 | f floor
85 |
86 | objectsChange =
87 | FloorDiff.diffObjects newFloor.objects floor.objects
88 | |> ObjectsChange.simplify
89 |
90 | changed =
91 | not <| ObjectsChange.isEmpty objectsChange
92 |
93 | newUndoList =
94 | if changed then
95 | UndoList.new newFloor efloor.undoList
96 | else
97 | efloor.undoList
98 | in
99 | ( { efloor | undoList = newUndoList }, objectsChange )
100 |
101 |
102 | syncObjects : ObjectsChange -> EditingFloor -> EditingFloor
103 | syncObjects change efloor =
104 | let
105 | undoList =
106 | efloor.undoList
107 |
108 | separated =
109 | ObjectsChange.separate change
110 |
111 | -- Unsafe operation!
112 | newUndoList =
113 | { undoList
114 | | present = Floor.addObjects (separated.added ++ separated.modified) undoList.present
115 | }
116 | in
117 | { efloor | undoList = newUndoList }
118 |
119 |
120 | undo : EditingFloor -> ( EditingFloor, ObjectsChange )
121 | undo efloor =
122 | let
123 | ( undoList, objectsChange ) =
124 | UndoList.undoReplace
125 | ObjectsChange.empty
126 | (\prev current ->
127 | let
128 | objectsChange =
129 | FloorDiff.diffObjects prev.objects current.objects
130 | in
131 | ( Floor.changeObjectsByChanges objectsChange current
132 | , objectsChange |> ObjectsChange.simplify
133 | )
134 | )
135 | efloor.undoList
136 | in
137 | ( { efloor | undoList = undoList }, objectsChange )
138 |
139 |
140 | redo : EditingFloor -> ( EditingFloor, ObjectsChange )
141 | redo efloor =
142 | let
143 | ( undoList, objectsChange ) =
144 | UndoList.redoReplace
145 | ObjectsChange.empty
146 | (\next current ->
147 | let
148 | objectsChange =
149 | FloorDiff.diffObjects next.objects current.objects
150 | in
151 | ( Floor.changeObjectsByChanges objectsChange current
152 | , objectsChange |> ObjectsChange.simplify
153 | )
154 | )
155 | efloor.undoList
156 | in
157 | ( { efloor | undoList = undoList }, objectsChange )
158 |
159 |
160 | present : EditingFloor -> Floor
161 | present efloor =
162 | efloor.undoList.present
163 |
--------------------------------------------------------------------------------
/src/elm/Model/SearchResult.elm:
--------------------------------------------------------------------------------
1 | module Model.SearchResult exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import CoreType exposing (..)
5 | import Model.Object as Object exposing (Object)
6 | import Model.Person exposing (Person)
7 |
8 |
9 | type alias PersonName =
10 | String
11 |
12 |
13 | type SearchResult
14 | = Object Object FloorId
15 | | MissingPerson PersonId
16 |
17 |
18 | type alias SearchResultsForOnePost =
19 | ( Maybe PersonName, List SearchResult )
20 |
21 |
22 | getPersonId : SearchResult -> Maybe Id
23 | getPersonId result =
24 | case result of
25 | Object o _ ->
26 | Object.relatedPerson o
27 |
28 | MissingPerson personId ->
29 | Just personId
30 |
31 |
32 | groupByPostAndReorder : Maybe String -> Dict String Person -> List SearchResult -> List SearchResultsForOnePost
33 | groupByPostAndReorder thisFloorId personInfo results =
34 | groupByPost personInfo results
35 | |> List.map (\( maybePostName, results ) -> ( maybePostName, reorderResults thisFloorId results ))
36 |
37 |
38 | groupByPost : Dict String Person -> List SearchResult -> List SearchResultsForOnePost
39 | groupByPost personInfo results =
40 | results
41 | |> groupBy
42 | (\result ->
43 | getPersonId result
44 | |> Maybe.andThen (\id -> Dict.get id personInfo)
45 | |> Maybe.map (\person -> ( person.post, Just person.post ))
46 | |> Maybe.withDefault ( "", Nothing )
47 | )
48 | |> Dict.values
49 |
50 |
51 | groupBy : (a -> ( comparable, b )) -> List a -> Dict comparable ( b, List a )
52 | groupBy f list =
53 | List.foldr
54 | (\a dict ->
55 | let
56 | ( key, realKey ) =
57 | f a
58 | in
59 | Dict.update key
60 | (\maybeValue ->
61 | case maybeValue of
62 | Just ( realKey, value ) ->
63 | Just ( realKey, a :: value )
64 |
65 | Nothing ->
66 | Just ( realKey, [ a ] )
67 | )
68 | dict
69 | )
70 | Dict.empty
71 | list
72 |
73 |
74 | reorderResults : Maybe String -> List SearchResult -> List SearchResult
75 | reorderResults thisFloorId results =
76 | let
77 | ( inThisFloor, inOtherFloor, missing ) =
78 | List.foldl
79 | (\result ( this, other, miss ) ->
80 | case result of
81 | Object _ floorId ->
82 | if Just floorId == thisFloorId then
83 | ( result :: this, other, miss )
84 | else
85 | ( this, result :: other, miss )
86 |
87 | MissingPerson _ ->
88 | ( this, other, result :: miss )
89 | )
90 | ( [], [], [] )
91 | results
92 | in
93 | inThisFloor ++ inOtherFloor ++ missing
94 |
95 |
96 | mergeObjectInfo : String -> List Object -> List SearchResult -> List SearchResult
97 | mergeObjectInfo currentFloorId objects results =
98 | List.concatMap
99 | (\result ->
100 | case result of
101 | Object object floorId ->
102 | if floorId == currentFloorId then
103 | case List.filter (\o -> Object.idOf object == Object.idOf o) objects of
104 | [] ->
105 | case Object.relatedPerson object of
106 | Just personId ->
107 | [ MissingPerson personId ]
108 |
109 | Nothing ->
110 | []
111 |
112 | _ ->
113 | [ result ]
114 | else
115 | [ result ]
116 |
117 | MissingPerson personId ->
118 | case List.filter (\object -> Object.relatedPerson object == Just personId) objects of
119 | [] ->
120 | [ result ]
121 |
122 | objects ->
123 | List.map (\object -> Object object currentFloorId) objects
124 | )
125 | results
126 |
127 |
128 | moveObject : String -> List Object -> List SearchResult -> List SearchResult
129 | moveObject oldFloorId newObjects results =
130 | List.map
131 | (\result ->
132 | case result of
133 | Object object floorId ->
134 | if floorId == oldFloorId then
135 | case List.filter (\o -> Object.idOf object == Object.idOf o) newObjects of
136 | newObject :: _ ->
137 | Object newObject (Object.floorIdOf newObject)
138 |
139 | _ ->
140 | result
141 | else
142 | result
143 |
144 | MissingPerson personId ->
145 | result
146 | )
147 | results
148 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/Msg.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.Msg exposing (..)
2 |
3 | import Time exposing (Time)
4 | import Debounce exposing (Debounce)
5 | import ContextMenu exposing (ContextMenu)
6 | import Model.Mode as Mode exposing (Mode(..), EditingMode(..))
7 | import Model.User as User exposing (User)
8 | import Model.Person as Person exposing (Person)
9 | import Model.Object as Object exposing (..)
10 | import Model.ObjectsChange exposing (ObjectsChange)
11 | import Model.Prototype exposing (Prototype)
12 | import Model.Prototypes as Prototypes exposing (Prototypes)
13 | import Model.Floor as Floor exposing (Floor, FloorBase)
14 | import Model.FloorInfo as FloorInfo exposing (FloorInfo)
15 | import Model.Errors as Errors exposing (GlobalError(..))
16 | import Model.I18n as I18n exposing (Language(..))
17 | import Model.SearchResult as SearchResult exposing (SearchResult)
18 | import Model.SaveRequest as SaveRequest exposing (SaveRequest(..))
19 | import Model.ColorPalette as ColorPalette exposing (ColorPalette)
20 | import Util.File exposing (File)
21 | import API.Cache as Cache exposing (Cache, UserState)
22 | import Component.FloorProperty as FloorProperty
23 | import Component.Header as Header exposing (..)
24 | import Component.ImageLoader as ImageLoader
25 | import Component.FloorDeleter as FloorDeleter
26 | import Page.Map.URL exposing (URL)
27 | import Page.Map.ContextMenuContext exposing (ContextMenuContext)
28 | import Page.Map.ClipboardOptionsView as ClipboardOptionsView
29 | import CoreType exposing (..)
30 |
31 |
32 | type alias Id =
33 | String
34 |
35 |
36 |
37 | {- TODO temporary here for sefe refactoring -}
38 |
39 |
40 | type ObjectNameInputMsg
41 | = NoOperation
42 | | CaretPosition Int
43 | | InputName ObjectId String Int
44 | | KeydownOnNameInput (List Person) ( Int, Int )
45 | | KeyupOnNameInput Int
46 | | SelectCandidate ObjectId PersonId
47 | | UnsetPerson ObjectId
48 |
49 |
50 | type Msg
51 | = NoOp
52 | | UrlUpdate (Result String URL)
53 | | Initialized (Maybe String) Bool UserState User
54 | | FloorsInfoLoaded Bool (List FloorInfo)
55 | | FloorLoaded (Maybe Floor)
56 | | ColorsLoaded ColorPalette
57 | | PrototypesLoaded (List Prototype)
58 | | ImageSaved String Int Int
59 | | RequestSave SaveRequest
60 | | SaveFloorDebounceMsg Debounce.Msg
61 | | ObjectsSaved ObjectsChange
62 | | UnlockSaveFloor
63 | | FloorSaved FloorBase
64 | | FloorPublished Floor
65 | | ClickOnCanvas
66 | | MouseDownOnCanvas Position
67 | | MouseUpOnCanvas Position
68 | | FocusCanvas
69 | | MouseDownOnObject Bool Bool Id Position
70 | | MouseUpOnObject Id Position
71 | | MouseDownOnResizeGrip Id Position
72 | | StartEditObject Id
73 | | Ctrl Bool
74 | | SelectBackgroundColor String
75 | | SelectColor String
76 | | SelectShape Object.Shape
77 | | SelectFontSize Float
78 | | InputObjectUrl (List ObjectId) String
79 | | ObjectNameInputMsg ObjectNameInputMsg
80 | | BeforeContextMenuOnObject Id Msg
81 | | ContextMenuMsg (ContextMenu.Msg ContextMenuContext)
82 | | GoToFloor String Bool
83 | | SelectSamePost String
84 | | SearchByPost String
85 | | GotSamePostPeople (List Person)
86 | | SelectIsland Id
87 | | SelectSameColor Id
88 | | WindowSize Size
89 | | MouseWheel Float Position
90 | | ChangeMode EditingMode
91 | | PrototypesMsg Prototypes.Msg
92 | | ClipboardOptionsMsg ( ClipboardOptionsView.Form, Maybe Size )
93 | | RegisterPrototype Id
94 | | FloorPropertyMsg FloorProperty.Msg
95 | | RotateObjects (List ObjectId)
96 | | FirstNameOnly (List Id)
97 | | RemoveSpaces (List Id)
98 | | HeaderMsg Header.Msg
99 | | SignIn
100 | | SignOut
101 | | ToggleEditing
102 | | TogglePrintView
103 | | SelectLang Language
104 | | UpdateSearchQuery String
105 | | SubmitSearch
106 | | GotSearchResult (List SearchResult) (List Person)
107 | | SelectSearchResult ObjectId FloorId (Maybe PersonId)
108 | | CloseSearchResult
109 | | StartDraggingFromMissingPerson String String
110 | | StartDraggingFromExistingObject Id String (Maybe String) String Time
111 | | CachePeople (List Person)
112 | | RequestCandidate Id String
113 | | SearchCandidateDebounceMsg Debounce.Msg
114 | | GotCandidateSelection Id (List Person)
115 | | GotMatchingList (List ( Id, List Person ))
116 | | UpdatePersonCandidate Id (List Id)
117 | | PreparePublish
118 | | GotDiffSource ( Floor, Maybe Floor )
119 | | CloseDiff
120 | | ConfirmDiff
121 | | ClosePopup
122 | | ShowDetailForObject Id
123 | | CreateNewFloor
124 | | CopyFloor FloorId Bool
125 | | EmulateClick Id Bool Time
126 | | TokenRemoved
127 | | Undo
128 | | Redo
129 | | Focused
130 | | PasteFromClipboard String
131 | | SyncFloor
132 | | MouseMove Position
133 | | MouseUp
134 | | ImageLoaderMsg ImageLoader.Msg
135 | | GotFileWithDataURL File String
136 | | FloorDeleterMsg FloorDeleter.Msg
137 | | FloorDeleted Floor
138 | | InsertEmoji String
139 | | ChangeToObjectUrl ObjectId
140 | | SetTransition Bool
141 | | Copy
142 | | Cut
143 | | Delete
144 | | MoveSelecedObjectsToward Direction
145 | | ShiftSelectionByTab
146 | | ExpandOrShrinkToward Direction
147 | | Print
148 | | FlipFloor
149 | | Error GlobalError
150 |
--------------------------------------------------------------------------------
/src/elm/Model/FloorDiff.elm:
--------------------------------------------------------------------------------
1 | module Model.FloorDiff exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import CoreType exposing (..)
5 | import Model.Object as Object exposing (..)
6 | import Model.Floor as Floor exposing (Floor)
7 | import Model.ObjectsChange as ObjectsChange exposing (..)
8 |
9 |
10 | type alias Options msg =
11 | { onClose : msg
12 | , onConfirm : msg
13 | , noOp : msg
14 | }
15 |
16 |
17 | type alias PropChanges =
18 | List ( String, String, String )
19 |
20 |
21 | diff : Floor -> Maybe Floor -> ( PropChanges, DetailedObjectsChange )
22 | diff new old =
23 | ( diffPropertyChanges new old
24 | , diffObjects new.objects (Maybe.withDefault Dict.empty (Maybe.map .objects old))
25 | )
26 |
27 |
28 | diffPropertyChanges : Floor -> Maybe Floor -> List ( String, String, String )
29 | diffPropertyChanges current prev =
30 | case prev of
31 | Just prev ->
32 | propertyChangesHelp current prev
33 |
34 | -- FIXME completely wrong
35 | Nothing ->
36 | (if Floor.name current /= "" then
37 | [ ( "Name", Floor.name current, "" ) ]
38 | else
39 | []
40 | )
41 | ++ (case current.realSize of
42 | Just ( w2, h2 ) ->
43 | [ ( "Size", "(" ++ toString w2 ++ ", " ++ toString h2 ++ ")", "" ) ]
44 |
45 | Nothing ->
46 | []
47 | )
48 |
49 |
50 | propertyChangesHelp : Floor -> Floor -> List ( String, String, String )
51 | propertyChangesHelp current prev =
52 | let
53 | nameChange =
54 | if Floor.name current == Floor.name prev then
55 | []
56 | else
57 | [ ( "Name", Floor.name current, Floor.name prev ) ]
58 |
59 | ordChange =
60 | if current.ord == prev.ord then
61 | []
62 | else
63 | [ ( "Order", toString current.ord, toString prev.ord ) ]
64 |
65 | sizeChange =
66 | if current.realSize == prev.realSize then
67 | []
68 | else
69 | case ( current.realSize, prev.realSize ) of
70 | ( Just ( w1, h1 ), Just ( w2, h2 ) ) ->
71 | [ ( "Size", "(" ++ toString w1 ++ ", " ++ toString h1 ++ ")", "(" ++ toString w2 ++ ", " ++ toString h2 ++ ")" ) ]
72 |
73 | ( Just ( w1, h1 ), Nothing ) ->
74 | [ ( "Size", "(" ++ toString w1 ++ ", " ++ toString h1 ++ ")", "" ) ]
75 |
76 | ( Nothing, Just ( w2, h2 ) ) ->
77 | [ ( "Size", "", "(" ++ toString w2 ++ ", " ++ toString h2 ++ ")" ) ]
78 |
79 | _ ->
80 | []
81 |
82 | -- should not happen
83 | imageChange =
84 | if current.image /= prev.image then
85 | [ ( "Image", Maybe.withDefault "" current.image, Maybe.withDefault "" prev.image ) ]
86 | else
87 | []
88 |
89 | flipImageChange =
90 | if current.flipImage /= prev.flipImage then
91 | [ ( "FlipImage", toString current.flipImage, toString prev.flipImage ) ]
92 | else
93 | []
94 | in
95 | nameChange ++ ordChange ++ sizeChange ++ imageChange ++ flipImageChange
96 |
97 |
98 | diffObjects : Dict ObjectId Object -> Dict ObjectId Object -> DetailedObjectsChange
99 | diffObjects newObjects oldObjects =
100 | Dict.merge
101 | (\id new dict -> Dict.insert id (ObjectsChange.Added new) dict)
102 | (\id new old dict ->
103 | case diffObjectProperty new old of
104 | [] ->
105 | dict
106 |
107 | list ->
108 | Dict.insert id (ObjectsChange.Modified { new = Object.copyUpdateAt old new, old = old, changes = list }) dict
109 | )
110 | (\id old dict -> Dict.insert id (ObjectsChange.Deleted old) dict)
111 | newObjects
112 | oldObjects
113 | Dict.empty
114 |
115 |
116 | diffObjectProperty : Object -> Object -> List ObjectPropertyChange
117 | diffObjectProperty new old =
118 | List.filterMap
119 | identity
120 | [ objectPropertyChange ChangeName Object.nameOf new old
121 | , objectPropertyChange ChangeSize Object.sizeOf new old
122 | , objectPropertyChange ChangePosition Object.positionOf new old
123 | , objectPropertyChange ChangeBackgroundColor Object.backgroundColorOf new old
124 | , objectPropertyChange ChangeColor Object.colorOf new old
125 | , objectPropertyChange ChangeFontSize Object.fontSizeOf new old
126 | , objectPropertyChange ChangeBold Object.isBold new old
127 | , objectPropertyChange ChangeUrl Object.urlOf new old
128 | , objectPropertyChange ChangeShape Object.shapeOf new old
129 | , objectPropertyChange ChangePerson Object.relatedPerson new old
130 | ]
131 |
132 |
133 | objectPropertyChange : (a -> a -> b) -> (Object -> a) -> Object -> Object -> Maybe b
134 | objectPropertyChange f toProperty new old =
135 | let
136 | newProp =
137 | toProperty new
138 |
139 | oldProp =
140 | toProperty old
141 | in
142 | if newProp /= oldProp then
143 | Just (f newProp oldProp)
144 | else
145 | Nothing
146 |
147 |
148 |
149 | --
150 |
--------------------------------------------------------------------------------
/src/elm/Page/Map/FloorsInfoView.elm:
--------------------------------------------------------------------------------
1 | module Page.Map.FloorsInfoView exposing (view)
2 |
3 | import Dict exposing (Dict)
4 | import Html exposing (..)
5 | import Html.Attributes exposing (..)
6 | import Html.Events exposing (..)
7 | import ContextMenu
8 | import CoreType exposing (..)
9 | import View.CommonStyles as CommonStyles
10 | import View.Styles as Styles
11 | import Model.User as User exposing (User)
12 | import Model.FloorInfo as FloorInfo exposing (FloorInfo(..))
13 | import Page.Map.Msg exposing (Msg(..))
14 | import Page.Map.ContextMenuContext as ContextMenuContext
15 |
16 |
17 | view : Bool -> User -> Bool -> Maybe String -> Dict FloorId FloorInfo -> Html Msg
18 | view disableContextmenu user isEditMode currentFloorId floorsInfo =
19 | if isEditMode then
20 | viewEditingFloors disableContextmenu user currentFloorId floorsInfo
21 | else
22 | viewPublicFloors currentFloorId floorsInfo
23 |
24 |
25 | viewEditingFloors : Bool -> User -> Maybe FloorId -> Dict FloorId FloorInfo -> Html Msg
26 | viewEditingFloors disableContextmenu user currentFloorId floorsInfo =
27 | let
28 | contextMenuMsg floor =
29 | if not disableContextmenu && not (User.isGuest user) then
30 | Just (ContextMenu.open ContextMenuMsg (ContextMenuContext.FloorInfoContextMenu floor.id))
31 | else
32 | Nothing
33 |
34 | floorList =
35 | floorsInfo
36 | |> FloorInfo.toValues
37 | |> List.map (\floorInfo -> ( FloorInfo.isNeverPublished floorInfo, FloorInfo.editingFloor floorInfo ))
38 | |> List.sortBy (Tuple.second >> .ord)
39 | |> List.map
40 | (\( isNeverPublished, floor ) ->
41 | eachView
42 | (contextMenuMsg floor)
43 | (GoToFloor floor.id True)
44 | (currentFloorId == Just floor.id)
45 | (if floor.temporary then
46 | Temporary
47 | else if isNeverPublished then
48 | Private
49 | else
50 | Public
51 | )
52 | floor.name
53 | )
54 |
55 | create =
56 | if User.isAdmin user then
57 | [ createButton ]
58 | else
59 | []
60 | in
61 | wrapList (floorList ++ create)
62 |
63 |
64 | viewPublicFloors : Maybe FloorId -> Dict FloorId FloorInfo -> Html Msg
65 | viewPublicFloors currentFloorId floorsInfo =
66 | floorsInfo
67 | |> FloorInfo.toPublicList
68 | |> List.map
69 | (\floor ->
70 | eachView
71 | Nothing
72 | (GoToFloor floor.id False)
73 | (currentFloorId == Just floor.id)
74 | Public
75 | floor.name
76 | )
77 | |> wrapList
78 |
79 |
80 | wrapList : List (Html msg) -> Html msg
81 | wrapList children =
82 | ul [ style floorsInfoViewStyle ] children
83 |
84 |
85 | eachView : Maybe (Attribute msg) -> msg -> Bool -> ColorType -> String -> Html msg
86 | eachView maybeOpenContextMenu onClickMsg selected colorType floorName =
87 | li
88 | (style (floorsInfoViewItemStyle selected colorType)
89 | :: onClick onClickMsg
90 | :: (maybeOpenContextMenu |> Maybe.map (List.singleton) |> Maybe.withDefault [])
91 | )
92 | [ span [ style floorsInfoViewItemLinkStyle ] [ text floorName ] ]
93 |
94 |
95 | createButton : Html Msg
96 | createButton =
97 | li
98 | [ style (floorsInfoViewItemStyle False Public)
99 | , onClick CreateNewFloor
100 | ]
101 | [ span [ style floorsInfoViewItemLinkStyle ] [ text "+" ] ]
102 |
103 |
104 | type alias Styles =
105 | List ( String, String )
106 |
107 |
108 | floorsInfoViewStyle : Styles
109 | floorsInfoViewStyle =
110 | [ ( "position", "absolute" )
111 | , ( "width", "calc(100% - 300px)" )
112 | , ( "z-index", Styles.zFloorInfo )
113 | ]
114 |
115 |
116 | type ColorType
117 | = Public
118 | | Private
119 | | Temporary
120 |
121 |
122 | floorsInfoViewItemStyle : Bool -> ColorType -> Styles
123 | floorsInfoViewItemStyle selected colorType =
124 | [ ( "background-color"
125 | , case colorType of
126 | Public ->
127 | "#fff"
128 |
129 | Private ->
130 | "#dbdbdb"
131 |
132 | Temporary ->
133 | "#dbdbaa"
134 | )
135 | , ( "border-right"
136 | , if selected then
137 | "solid 2px " ++ CommonStyles.selectColor
138 | else
139 | "solid 1px #d0d0d0"
140 | )
141 | , ( "border-bottom"
142 | , if selected then
143 | "solid 2px " ++ CommonStyles.selectColor
144 | else
145 | "solid 1px #d0d0d0"
146 | )
147 | , ( "border-top"
148 | , if selected then
149 | "solid 2px " ++ CommonStyles.selectColor
150 | else
151 | "none"
152 | )
153 | , ( "border-left"
154 | , if selected then
155 | "solid 2px " ++ CommonStyles.selectColor
156 | else
157 | "none"
158 | )
159 | , ( "min-width", "72px" )
160 | , ( "box-sizing", "border-box" )
161 | , ( "height", "30px" )
162 | , ( "position", "relative" )
163 | , ( "font-size", "12px" )
164 | , ( "float", "left" )
165 | , ( "cursor", "pointer" )
166 | ]
167 |
168 |
169 | floorsInfoViewItemLinkStyle : Styles
170 | floorsInfoViewItemLinkStyle =
171 | [ ( "display", "block" )
172 | , ( "text-align", "center" )
173 | , ( "vertical-align", "middle" )
174 | , ( "line-height", "30px" )
175 | , ( "padding", "0 8px" )
176 | ]
177 |
--------------------------------------------------------------------------------
/src/elm/Model/Prototypes.elm:
--------------------------------------------------------------------------------
1 | module Model.Prototypes exposing (..)
2 |
3 | import Model.Prototype exposing (Prototype)
4 | import Model.ObjectsOperation as ObjectsOperation
5 | import Util.ListUtil exposing (..)
6 | import CoreType exposing (..)
7 |
8 |
9 | type alias PositionedPrototype =
10 | ( Prototype, Position )
11 |
12 |
13 | type alias Prototypes =
14 | { data : List Prototype
15 | , selected : Int
16 | }
17 |
18 |
19 | gridSize : Int
20 | gridSize =
21 | 8
22 |
23 |
24 |
25 | --TODO
26 |
27 |
28 | init : List Prototype -> Prototypes
29 | init data =
30 | { data = data
31 | , selected = 0 -- index
32 | }
33 |
34 |
35 | type Msg
36 | = SelectPrev
37 | | SelectNext
38 |
39 |
40 | prev : Msg
41 | prev =
42 | SelectPrev
43 |
44 |
45 | next : Msg
46 | next =
47 | SelectNext
48 |
49 |
50 | update : Msg -> Prototypes -> Prototypes
51 | update msg model =
52 | case msg of
53 | SelectPrev ->
54 | { model
55 | | selected = max 0 (model.selected - 1) -- fail safe
56 | }
57 |
58 | SelectNext ->
59 | { model
60 | | selected = min (List.length model.data - 1) (model.selected + 1) -- fail safe
61 | }
62 |
63 |
64 | register : Prototype -> Prototypes -> Prototypes
65 | register prototype model =
66 | let
67 | newPrototypes =
68 | model.data ++ [ prototype ]
69 | in
70 | { model
71 | | data = newPrototypes
72 | , selected = List.length newPrototypes - 1
73 | }
74 |
75 |
76 | selectedPrototype : Prototypes -> Prototype
77 | selectedPrototype model =
78 | findPrototypeByIndex model.selected model.data
79 |
80 |
81 | findPrototypeByIndex : Int -> List Prototype -> Prototype
82 | findPrototypeByIndex index list =
83 | case getAt index list of
84 | Just prototype ->
85 | prototype
86 |
87 | Nothing ->
88 | case List.head list of
89 | Just prototype ->
90 | prototype
91 |
92 | Nothing ->
93 | Debug.crash "no prototypes found"
94 |
95 |
96 | prototypes : Prototypes -> List ( Prototype, Bool )
97 | prototypes model =
98 | model.data
99 | |> List.indexedMap
100 | (\index prototype ->
101 | ( prototype, model.selected == index )
102 | )
103 |
104 |
105 | stampIndices : Bool -> Size -> Position -> Position -> ( List Int, List Int )
106 | stampIndices horizontal deskSize pos1 pos2 =
107 | let
108 | ( amountX, amountY ) =
109 | if horizontal then
110 | let
111 | amountX =
112 | (abs (pos2.x - pos1.x) + deskSize.width // 2) // deskSize.width
113 |
114 | amountY =
115 | if abs (pos2.y - pos1.y) > (deskSize.height // 2) then
116 | 1
117 | else
118 | 0
119 | in
120 | ( amountX, amountY )
121 | else
122 | let
123 | amountX =
124 | if abs (pos2.x - pos1.x) > (deskSize.width // 2) then
125 | 1
126 | else
127 | 0
128 |
129 | amountY =
130 | (abs (pos2.y - pos1.y) + deskSize.height // 2) // deskSize.height
131 | in
132 | ( amountX, amountY )
133 | in
134 | ( List.map
135 | (\i ->
136 | if pos2.x > pos1.x then
137 | i
138 | else
139 | -i
140 | )
141 | (List.range 0 amountX)
142 | , List.map
143 | (\i ->
144 | if pos2.y > pos1.y then
145 | i
146 | else
147 | -i
148 | )
149 | (List.range 0 amountY)
150 | )
151 |
152 |
153 | generateAllCandidatePosition : Size -> Position -> ( List Int, List Int ) -> List Position
154 | generateAllCandidatePosition deskSize centerPos ( indicesX, indicesY ) =
155 | let
156 | lefts =
157 | List.map (\index -> centerPos.x + deskSize.width * index) indicesX
158 |
159 | tops =
160 | List.map (\index -> centerPos.y + deskSize.height * index) indicesY
161 | in
162 | List.concatMap (\left -> List.map (\top -> Position left top) tops) lefts
163 |
164 |
165 | positionedPrototypesOnDragging : Int -> Prototype -> Position -> Position -> List PositionedPrototype
166 | positionedPrototypesOnDragging gridSize prototype xy1 xy2 =
167 | -- imagePos
168 | let
169 | x1 =
170 | xy1.x
171 |
172 | y1 =
173 | xy1.y
174 |
175 | x2 =
176 | xy2.x
177 |
178 | y2 =
179 | xy2.y
180 |
181 | deskSize =
182 | ( prototype.width, prototype.height )
183 |
184 | flip ( w, h ) =
185 | ( h, w )
186 |
187 | horizontal =
188 | abs (x2 - x1) > abs (y2 - y1)
189 |
190 | ( deskWidth, deskHeight ) =
191 | if horizontal then
192 | flip deskSize
193 | else
194 | deskSize
195 |
196 | ( indicesX, indicesY ) =
197 | stampIndices horizontal (Size deskWidth deskHeight) xy1 xy2
198 |
199 | center =
200 | ObjectsOperation.fitPositionToGrid
201 | gridSize
202 | (Position (x1 - Tuple.first deskSize // 2) (y1 - Tuple.second deskSize // 2))
203 |
204 | all =
205 | generateAllCandidatePosition
206 | (Size deskWidth deskHeight)
207 | center
208 | ( indicesX, indicesY )
209 |
210 | prototype_ =
211 | { prototype
212 | | width = deskWidth
213 | , height = deskHeight
214 | }
215 | in
216 | List.map ((,) prototype_) all
217 |
--------------------------------------------------------------------------------