diff --git a/public/components/w95/ContextButton.jsx b/public/components/w95/ContextButton.jsx new file mode 100644 index 0000000..aa2e1f2 --- /dev/null +++ b/public/components/w95/ContextButton.jsx @@ -0,0 +1,88 @@ +import React, { Component } from 'react'; +import { render } from 'react-dom'; + +import ContextMenuButton from './ContextMenuButton'; + +class ContextButton extends Component { + constructor(props) { + super(props); + + this.handleClick = function(e) { + if(this.isOpen()) { + //Already active, close menu. + this.props.menu.closeMenu(); + } else { + this.props.menu.openMenu(this.props.selfRef, this); + } + e.stopPropagation(); + }.bind(this); + + this.handleHover = function() { + this.props.menu.hoverMenu(this.props.selfRef, this); + }.bind(this); + } + + componentDidMount() { + let e = this.refs[this.props.selfRef]; + + e.addEventListener('click', this.handleClick); + e.addEventListener('mouseover', this.handleHover); + } + + componentWillUmount() { + let e = this.refs[this.props.selfRef]; + + e.removeEventListener('click', this.handleClick); + e.removeEventListener('mouseover', this.handleHover); + } + + open() { + if(this.isDisabled()) return this.hide(); + this.refs[this.props.selfRef].addClass("active"); + } + + isDisabled() {return this.refs[this.props.selfRef].hasClass("disabled");} + + hide() { + this.refs[this.props.selfRef].removeClass("active"); + } + + isOpen() { + return this.refs[this.props.selfRef].hasClass("active"); + } + + render() { + let cls = "btn"; + + let options = []; + if(this.props.data === "disabled") { + cls += " disabled"; + } else { + let opts = Object.keys(this.props.data); + for(let i = 0; i < opts.length; i++) { + let k = opts[i]; + let o = this.props.data[k]; + //options.push(
{k}
); + options.push(); + } + } + + let menu =
; + if(options.length > 0) { + menu = ( +
+ {options} +
+ ); + } + + return ( +
+ {this.props.title} + {menu} +
+ ) + } +} + +export default ContextButton; diff --git a/public/components/w95/ContextMenu.jsx b/public/components/w95/ContextMenu.jsx new file mode 100644 index 0000000..eede9fa --- /dev/null +++ b/public/components/w95/ContextMenu.jsx @@ -0,0 +1,69 @@ +import React, { Component } from 'react'; +import { render } from 'react-dom'; + +import ContextButton from './ContextButton'; +import ContextMenuButton from './ContextMenuButton'; + +class ContextMenu extends Component { + constructor(props) { + super(props); + + this.handleClickOutside = function(e) { + this.closeMenu(); + e.stopPropagation(); + }.bind(this); + } + + isOpen() { + return this.open; + } + + openMenu(ref, el) { + let keys = Object.keys(this.refs); + for(let i = 0; i < keys.length; i++) { + this.refs[keys[i]].hide(ref, el); + } + el.open(); + this.open = true; + } + + closeMenu() { + let keys = Object.keys(this.refs); + for(let i = 0; i < keys.length; i++) { + this.refs[keys[i]].hide(); + } + this.open = false; + } + + hoverMenu(ref, el) { + if(!this.isOpen()) return; + this.openMenu(ref, el); + } + + componentDidMount() { + document.addEventListener('click', this.handleClickOutside); + } + + componentWillUmount() { + document.removeEventListener('click', this.handleClickOutside); + } + + render() { + let contextButtons = []; + let btnKeys = Object.keys(this.props.menu); + for(let i = 0; i < btnKeys.length; i++) { + let key = btnKeys[i]; + var b = this.props.menu[key]; + if(b === false) continue; + contextButtons.push(); + } + + return ( +
+ {contextButtons} +
+ ); + } +} + +export default ContextMenu; diff --git a/public/components/w95/ContextMenuButton.jsx b/public/components/w95/ContextMenuButton.jsx new file mode 100644 index 0000000..98b9647 --- /dev/null +++ b/public/components/w95/ContextMenuButton.jsx @@ -0,0 +1,45 @@ +import React, { Component } from 'react'; +import { render } from 'react-dom'; + +class ContextMenuButton extends Component { + constructor(props) { + super(props); + + this.handleClick = function(e) { + e.stopPropagation(); + if(this.isDisabled()) return; + this.props.button.props.menu.closeMenu(); + this.clicked(); + }.bind(this); + } + + clicked() { + if(typeof this.props.data === 'function') { + this.props.data(); + } + } + + componentDidMount() { + this.refs.option.addEventListener('click', this.handleClick); + } + + componentWillUmount() { + this.refs.option.removeEventListener('click', this.handleClick); + } + + isDisabled() {return this.props.data === "disabled";} + + render() { + let cls = "menu-option"; + + if(this.isDisabled()) cls += " disabled"; + + return ( +
+ {this.props.title} +
+ ); + } +} + +export default ContextMenuButton; diff --git a/public/components/w95/Window95.jsx b/public/components/w95/Window95.jsx new file mode 100644 index 0000000..8f93936 --- /dev/null +++ b/public/components/w95/Window95.jsx @@ -0,0 +1,75 @@ +import React, { Component } from 'react'; + +import ContextMenuButton from './ContextMenuButton'; +import ContextButton from './ContextButton'; +import ContextMenu from './ContextMenu'; + +const defaultButtons = { + maximize: false, + minimize: "disabled", + close: true +}; + +const defaultMenus = { + "File": { + "New...": true, + "Open...": "disabled", + "Exit": true, + }, + + "Edit": "disabled", + "Help": { + "View Help...": true, + "About domsPlace();": true + } +}; + +class Window95 extends Component { + constructor(props) { + super(props); + + this.state = { + title: this.props.title ? this.props.title : "Untitled", + buttons: this.props.buttons ? this.props.buttons : defaultButtons, + menu: this.props.menu ? this.props.menu : defaultMenus + }; + } + + render() { + let btns = []; + let btnKeys = Object.keys(this.state.buttons); + for(let i = 0; i < btnKeys.length; i++) { + let key = btnKeys[i]; + var b = this.state.buttons[key]; + if(b === false) continue; + let cls = "btn " + btnKeys[i]; + if(b !== true && b !== false) cls += " " + b; + btns.push(
); + } + + let menu =
+ if(this.state.menu !== "false") { + menu = ; + } + + let clss = "window "; + if(this.props.className) clss += this.props.className; + + return ( +
+
+
+ {this.state.title} +
+ {btns} +
+
+ {menu} + + {this.props.children} +
+ ); + } +} + +export default Window95; diff --git a/public/images/95/1x/95button.png b/public/images/95/1x/95button.png new file mode 100644 index 0000000..e2a2360 Binary files /dev/null and b/public/images/95/1x/95button.png differ diff --git a/public/images/95/1x/95button_icons.png b/public/images/95/1x/95button_icons.png new file mode 100644 index 0000000..9a63892 Binary files /dev/null and b/public/images/95/1x/95button_icons.png differ diff --git a/public/images/95/1x/95button_inverted.png b/public/images/95/1x/95button_inverted.png new file mode 100644 index 0000000..64467d5 Binary files /dev/null and b/public/images/95/1x/95button_inverted.png differ diff --git a/public/images/95/1x/95frame.png b/public/images/95/1x/95frame.png new file mode 100644 index 0000000..f3a7c95 Binary files /dev/null and b/public/images/95/1x/95frame.png differ diff --git a/public/images/95/1x/95window.png b/public/images/95/1x/95window.png new file mode 100644 index 0000000..550dbc3 Binary files /dev/null and b/public/images/95/1x/95window.png differ diff --git a/public/images/95/2x/95button.png b/public/images/95/2x/95button.png new file mode 100644 index 0000000..acb58ef Binary files /dev/null and b/public/images/95/2x/95button.png differ diff --git a/public/images/95/2x/95button_icons.png b/public/images/95/2x/95button_icons.png new file mode 100644 index 0000000..3fe10c0 Binary files /dev/null and b/public/images/95/2x/95button_icons.png differ diff --git a/public/images/95/2x/95button_inverted.png b/public/images/95/2x/95button_inverted.png new file mode 100644 index 0000000..569ebb6 Binary files /dev/null and b/public/images/95/2x/95button_inverted.png differ diff --git a/public/images/95/2x/95frame.png b/public/images/95/2x/95frame.png new file mode 100644 index 0000000..91834b8 Binary files /dev/null and b/public/images/95/2x/95frame.png differ diff --git a/public/images/95/2x/95window.png b/public/images/95/2x/95window.png new file mode 100644 index 0000000..ab1e1b8 Binary files /dev/null and b/public/images/95/2x/95window.png differ diff --git a/public/images/dotted_bg_yellow.png b/public/images/dotted_bg_yellow.png new file mode 100644 index 0000000..7eecf05 Binary files /dev/null and b/public/images/dotted_bg_yellow.png differ diff --git a/public/styles/components/_w95.scss b/public/styles/components/_w95.scss new file mode 100644 index 0000000..2c1f4da --- /dev/null +++ b/public/styles/components/_w95.scss @@ -0,0 +1,196 @@ +$windowBG: #C0C0C0; +$highlight: #0000BF; +$disabled: #808080; + +$w95Font: 'MS PGothic', Verdana, Arial, Helvetica, sans-serif; +$imageScale: 1; + +@mixin border95($thickness) { + border: (3px * $thickness) solid black; + border-image-source: url('./../images/95/'+($thickness * $imageScale)+'x/95window.png'); + border-image-slice: 3 * $thickness; +} + +@mixin button95($thickness) { + border: (2px * $thickness) solid black; + border-image-source: url('./../images/95/'+($thickness * $imageScale)+'x/95button.png'); + border-image-slice: 2 * $thickness; + display: inline-block; + color: black; + background-image: url('./../images/95/'+($thickness * $imageScale)+'x/95button_icons.png'); + background-color: $windowBG; + background-position: 0px 0px; + background-size: 48px*$thickness 20px*$thickness; + + &:active { + border-image-source: url('./../images/95/'+($thickness * $imageScale)+'x/95button_inverted.png'); + } + + &.disabled { + background-position-y: 10px*$thickness; + &:active { + border-image-source: url('./../images/95/'+($thickness * $imageScale)+'x/95button.png'); + } + } +} + +@mixin frame95($thickness) { + border: (2px * $thickness) solid black; + border-image-source: url('./../images/95/'+($thickness * $imageScale)+'x/95frame.png'); + border-image-slice: 2 * $thickness; +} + +@mixin window95($scale) { + @extend %no-select; + @include border95($scale); + background: $windowBG; + font-family: $w95Font; + font-size: 12px*$scale; + display: inline-block; + + > .load_me_stuff { + position: fixed; + left: -1000vmax; + top: -1000vmax; + border-image-source: url('./../images/95/'+($scale * $imageScale)+'x/95button_inverted.png'); + } + + > .title { + width: 100%; + color: white; + background: #000080; + padding: 2px * $scale; + font-weight: bold; + margin-bottom: 1px * $scale; + + > .icon { + + } + + > .buttons { + height: 100%; + float: right; + font-size: 0; + + > .btn { + @include button95($scale); + width: 16px*$scale; + height: 14px*$scale; + font-size: 12px*$scale; + + &.close { + margin-left: 2px*$scale; + background-position-x: 0px*$scale; + } + + &.help { + background-position-x: 36px*$scale; + } + + &.minimize { + background-position-x: 24px*$scale; + } + + &.maximize { + background-position-x: 12px*$scale; + } + } + } + } + + > .context-menu { + width: 100%; + overflow: visible; + + > .btn { + padding: (1px*$scale) (5px*$scale); + border: 1px*$scale solid transparent; + display: inline-block; + position: relative; + + &::first-letter { + text-decoration: underline; + } + + &:hover { + border-bottom-color: #868686; + border-right-color: #868686; + border-top-color: #FFFFFF; + border-left-color: #FFFFFF; + } + + &.active { + border-right-color: #FFFFFF; + border-bottom-color: #FFFFFF; + border-top-color: #868686; + border-left-color: #868686; + + > .menu { + display: block; + } + } + + &.disabled { + color: $disabled; + &:hover,.active { + border-right-color: transparent; + border-bottom-color: transparent; + border-top-color: transparent; + border-left-color: transparent; + } + } + + > .menu { + @include border95($scale); + border-top-width: 2px*$scale; + position: absolute; + display: none; + background: $windowBG; + left: -1px*$scale; + margin-top: 2px*$scale; + width: auto; + + > .menu-option { + padding-left: 22px*$scale; + padding-right: 22px*$scale; + padding-bottom: 3px*$scale; + padding-top: 1px*$scale; + white-space: nowrap; + + &:hover { + background: $highlight; + color: white; + } + + &:first-child {font-weight: bold;} + + &::first-letter { + text-decoration: underline; + } + + &.disabled { + color: $disabled; + &:hover {background: inherit;color: $disabled;} + } + } + } + } + } + + &.inactive { + > .title { + background-color: $disabled; + } + } + + > .textarea { + @include frame95($scale); + background: white; + color: black; + cursor: text; + } +} + +.window { + @include window95(2); +}