Olga_Brekhuntsova 2 سال پیش
والد
کامیت
2cfa4ff12b

+ 104 - 0
hw-js-18-2-canvas/.gitignore

@@ -0,0 +1,104 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port

+ 12 - 0
hw-js-18-2-canvas/.prettierrc.json

@@ -0,0 +1,12 @@
+{
+    "printWidth": 80,
+    "tabWidth": 2,
+    "useTabs": false,
+    "semi": true,
+    "singleQuote": true,
+    "trailingComma": "all",
+    "bracketSpacing": true,
+    "jsxBracketSameLine": false,
+    "arrowParens": "avoid",
+    "proseWrap": "always"
+  }

+ 3 - 0
hw-js-18-2-canvas/.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+  "liveServer.settings.port": 5503
+}

+ 33 - 0
hw-js-18-2-canvas/README.md

@@ -0,0 +1,33 @@
+# HW-JS-18-2-Canvas
+
+Line: In и Select работают
+
+Ellipse: Не работала отрисовка при протягивании мыши справа налево (при уходе в
+отрицательную ось), т.е. радиус не может быть отрицательным. Исправила, взяв
+модуль числа
+
+Rectangle: Не работал select для прямоугольников, нарисованных справа налево
+и/или снизу-вверх. Исправила, введя доп.условия в in для отрицательных ширины и
+высоты.
+
+inBounds: при описании логики выделения области в событии mouseup, срабатывало
+также событие click. По несколько раз (на каждом элементе). Остановить обработку
+события click в коде mouseup по-легкому не смогла (погуглила и решения не нашла:
+все что пробовала, не срабатывало). Потому логику обработки выделения элементов,
+входящих в область, также вынесла в click. (установила условие, по которому
+click обрабатывается или как выделение области, или как клик на элементе).
+Вроде, работает корректно.
+
+Circle.inBounds: подправила логику. Ранее было: если захватил больше половины
+круга, то он считался выделенным. Теперь: круг выделяется только если область
+выделения его полностью захватывает. Добавила условие выделения, если при
+выделении ведешь мышкой влево и/или вверх.
+
+Ellipse и Rectangle.inBounds: по образу и подобию Circle. Только у Rectangle
+условия посложнее
+
+Line.inBounds: учтена ширина линии
+
+При выделении "редактор" иногда зависает. При обновлении все работает.
+
+Хостинг http://hw_js_18_2.olgapistryak.fe.a-level.com.ua/

+ 35 - 0
hw-js-18-2-canvas/index.css

@@ -0,0 +1,35 @@
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+}
+
+button {
+  width: 100%;
+  font-size: 2em;
+}
+input,
+button,
+select {
+  width: 100%;
+  font-size: 2em;
+}
+
+table {
+  border: 1px;
+  border-collapse: collapse;
+}
+
+td,
+th {
+  border: 1px solid black;
+}
+
+/*div#content {*/
+/*display: none;*/
+/*}*/

+ 31 - 0
hw-js-18-2-canvas/index.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width">
+  <title>eval</title>
+  <link href="index.css" rel="stylesheet" type="text/css" />
+  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.1.3/papaparse.min.js"></script>
+  <script src="http://gitlab.a-level.com.ua/gitgod/nanobind/raw/master/static/nb.js"></script>
+</head>
+
+<body>
+  <canvas id='canvas' width=1000 height=400></canvas>
+  <button id='undo'>UNDO</button>
+  <input type='color' id='color'>
+  <select id='tool'>
+    <option value='graffity'>Graffity</option>
+    <option value='circle'>Circle</option>
+    <option value='ellipse'>Ellipse</option>
+    <option value='line'>Line</option>
+    <option value='rectangle'>Rectangle</option>
+    <option value='select'>Select</option>
+  </select>
+  <input type='number' id='size' value=10>
+  <button id='delete'>Delete...</button>
+  <script src="index.js"></script>
+</body>
+
+</html>

+ 417 - 0
hw-js-18-2-canvas/index.js

@@ -0,0 +1,417 @@
+const canvas = document.getElementById('canvas')
+const ctx    = canvas.getContext('2d')
+
+const width  = canvas.width;
+const height = canvas.height;
+
+let current;
+let selection = []
+
+const tools = {
+    graffity: {
+        mousemove(e){ //e.buttons 0b00000x11 & 0b00000100 == x
+            (e.buttons & 0b001) && new Circle(e.layerX, e.layerY, +size.value, color.value)
+        }
+    },
+    circle: {
+        mousedown(e){
+            current = new Circle(e.layerX,e.layerY, 1, color.value)
+        },
+        mousemove(e){
+            if (!current) return;
+
+            current.radius = current.distanceTo(e.layerX, e.layerY)
+            Drawable.drawAll()
+        },
+
+        mouseup(e){
+            current = null
+        }
+    },
+       ellipse: {
+        mousedown(e){
+            current = new Ellipse(e.layerX, e.layerY, 0, 0, color.value, +size.value)
+        },
+        mousemove(e){
+            if (!current) return;
+
+            current.width = e.layerX - current.x
+            current.height = e.layerY - current.y
+
+            Drawable.drawAll()
+        },
+
+        mouseup(e){
+            current = null
+        }
+    },
+    line: { 
+        mousedown(e){
+            current = new Line(e.layerX, e.layerY, 0, 0, color.value, +size.value)
+        },
+        mousemove(e){
+            if (!current) return;
+
+            current.width = e.layerX - current.x
+            current.height = e.layerY - current.y
+
+            Drawable.drawAll()
+        },
+
+        mouseup(e){
+            current = null
+        }
+    },
+      rectangle: { 
+        mousedown(e){
+            current = new Rectangle(e.layerX, e.layerY, 0, 0, color.value, +size.value)
+        },
+        mousemove(e){
+            if (!current) return;
+
+            current.width = e.layerX - current.x
+            current.height = e.layerY - current.y
+            Drawable.drawAll()
+        },
+
+        mouseup(e){
+            current = null
+        }
+    },
+    select: {
+        click(e) {
+            selection = [];
+          
+            e.stopImmediatePropagation();
+                if (current.width||current.height){
+                let foundInBounds = Drawable.instances.filter(c => c.inBounds && c.inBounds(current.x, current.y, current.width, current.height))
+                    console.log('foundInBounds:');
+                    console.log(foundInBounds);
+                if (foundInBounds.length) {
+                     selection = [...foundInBounds];
+                                                             
+                    } 
+                  Drawable.instances.pop();     
+            }
+            else {
+                if (!selection.length) {
+                    let found = Drawable.instances.filter(c => c.in && c.in(e.layerX, e.layerY))
+                    console.log('found:')
+                    console.log(found)
+                    if (found.length) {
+                        if (e.ctrlKey) {
+                            selection.push(found.pop())
+                        }
+                        else {
+                            selection = [found.pop()]
+                        }
+                    }
+                    else {
+                        if (!e.ctrlKey) selection = []
+                    }
+                }
+            }
+            
+            Drawable.drawAll(selection)
+            current = null;
+      
+        },
+        mousedown(e){
+            //
+current = new BoundsRectangle(e.layerX, e.layerY, 0, 0, 'grey', +size.value)
+                   },
+        mousemove(e) {
+             if (!current) return;
+            current.width = e.layerX - current.x
+            current.height = e.layerY - current.y
+             Drawable.drawAll()
+        },
+
+        mouseup(e){
+            //x,y, w, h прямоугольника
+            //selection - только те элеменеты Drawable.instances которые в границах прямоугольника.
+            {
+                console.log(e);
+                       }
+                  },
+           }
+}
+
+
+
+function superHandler(evt){
+    let t = tools[tool.value]
+    if (typeof t[evt.type] === 'function')
+        t[evt.type].call(this, evt)
+}
+
+canvas.onmousemove = superHandler
+canvas.onmouseup   = superHandler
+canvas.onmousedown = superHandler
+canvas.onclick = superHandler
+
+////
+
+
+function Drawable(){
+   Drawable.addInstance(this);   
+}
+
+const distance = (x1,y1, x2, y2) => ((x1-x2)**2 + (y1-y2)**2)**0.5
+
+Drawable.prototype.draw = function(){};
+Drawable.prototype.distanceTo = function(x,y){
+    if (typeof this.x !== 'number' ||
+        typeof this.y !== 'number'){
+        return NaN
+    }
+    return distance(this.x, this.y, x, y)
+};
+Drawable.instances = [];
+Drawable.addInstance = function(item){
+    Drawable.instances.push(item);
+}
+
+Drawable.drawAll = function(selection=[]){
+    ctx.clearRect(0,0,width,height);
+    Drawable.forAll(item => item.draw())
+    selection.forEach(item => item.draw(true))
+   }
+
+Drawable.forAll = function(callback){
+    for(var i = 0; i<Drawable.instances.length;i++){
+        callback(Drawable.instances[i])
+    }
+}
+
+class Circle extends Drawable {
+    //__proto__: Drawable.prototype
+    constructor(x,y,radius, color){
+        super()
+        this.x      = x;
+        this.y      = y;
+        this.radius = radius;
+        this.color  = color;
+
+        this.draw(); 
+    }
+
+    draw(selected){
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
+        ctx.closePath();
+        ctx.fillStyle = this.color;
+        if (selected){
+            ctx.lineWidth = 2
+            ctx.stroke();
+        }
+        ctx.fill();
+    }
+
+    in(x,y){
+        return this.distanceTo(x,y) < this.radius
+    }
+
+    inBounds(x, y, w, h) { // x = 100, this.x = 102, w = 5
+        return  ((w>0)?(this.x-this.radius >= x && this.x+this.radius <= x + w):(this.x-this.radius >= x+w && this.x+this.radius <= x)) &&
+               ((h>0)?(this.y-this.radius >= y && this.y+this.radius <= y + h ):(this.y-this.radius >= y+h && this.y+this.radius <= y ))
+    }
+}
+
+class Ellipse extends Drawable {
+    //__proto__: Drawable.prototype
+   constructor(x,y, width, height, color, lineWidth){
+        super()
+        this.x      = x;
+        this.y      = y;
+        this.width  = width;
+        this.height = height;
+        this.color  = color;
+       this.lineWidth = lineWidth;
+         this.draw(); 
+    }
+
+       draw(selected){
+        ctx.beginPath();
+        ctx.moveTo(this.x, this.y);
+           ctx.ellipse(this.x, this.y, ((this.width**2)**0.5), ((this.height**2)**0.5), 0, 0, Math.PI * 2)
+          ctx.closePath();
+        ctx.strokeStyle = this.color;
+           ctx.fillStyle = this.color;
+        ctx.lineWidth = this.lineWidth;
+         if (selected){
+            ctx.lineWidth = 2
+            ctx.stroke();
+        }
+        ctx.fill();
+    }
+   
+    in (x, y){
+            return (
+               ((((x - this.x) ** 2) / ((this.width ) ** 2)) + (((y - this.y) ** 2 )/( (this.height) ** 2))) <= 1
+              )
+    }
+   inBounds(x, y, w, h) { 
+        return  ((w>0)?(this.x-this.width >= x && this.x+this.width <= x + w):(this.x-this.width >= x+w && this.x+this.width <= x)) &&
+               ((h>0)?(this.y-this.height >= y && this.y+this.height <= y + h ):(this.y-this.height >= y+h && this.y+this.height <= y ))
+    }
+  
+}
+
+
+class Line extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку
+    constructor(x,y, width, height, color, lineWidth){
+        super()
+        this.x      = x;
+        this.y      = y;
+        this.width  = width;
+        this.height = height;
+        this.color  = color;
+        this.lineWidth  = lineWidth;
+
+
+        this.draw(); 
+    }
+    
+
+    draw(){
+        ctx.beginPath();
+        ctx.moveTo(this.x, this.y);
+        ctx.lineTo(this.x + this.width, this.y + this.height);
+        ctx.closePath();
+        ctx.strokeStyle = this.color;
+        ctx.lineWidth   = this.lineWidth
+        ctx.stroke();
+    }
+
+    in(x, y) {
+        const deltaAngle = Math.atan2(y - this.y, x - this.x) - Math.atan2(this.height, this.width) ;
+        const newX = Math.cos(deltaAngle) * this.distanceTo(x, y);
+        const newY = Math.sin(deltaAngle) * this.distanceTo(x, y);
+        return ((newX>=0)&&(newX<=distance(0,0, this.width, this.height))&&(newY>=-this.lineWidth/2)&&(newY<=this.lineWidth/2)
+        )
+    }
+       inBounds(x, y, w, h) { 
+        let areaX = false;
+           let areaY = false;
+           const angleX = Math.atan2(this.height, this.width);
+                       const deltaX = Math.sin(angleX) * this.lineWidth/2;
+            const deltaY = Math.cos(angleX) * this.lineWidth/2;
+
+        if (this.width > 0) { areaX=(w > 0) ? (this.x-deltaX >= x && this.x + this.width+deltaX <= x + w):(this.x-deltaX >= x + w && this.x + this.width+deltaX <= x)}
+        else { areaX= ((w > 0) ? (this.x+ this.width-deltaX >= x && this.x+deltaX <= x + w):(this.x + this.width-deltaX>= x + w && this.x+deltaX <= x)) }
+          if (this.height > 0) { areaY=(h > 0) ? (this.y-deltaY >= y && this.y + this.height+deltaY <= y + h):(this.y-deltaY >= y + h && this.y + this.height+deltaY <= y)}
+        else { areaY= (h > 0) ? (this.y+ this.height-deltaY >= y && this.y+deltaY <= y + h):(this.y + this.height-deltaY>= y + h && this.y+deltaY<= y) }
+return areaX && areaY;
+                   }
+} 
+
+color.onchange = () => {
+    selection.forEach(c => c.color = color.value)
+    Drawable.drawAll(selection)
+}
+
+document.getElementById('delete').onclick = () =>{
+    Drawable.instances = Drawable.instances.filter(item => !selection.includes(item))
+    selection = []
+    Drawable.drawAll()
+}
+
+class Rectangle extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку
+    constructor(x, y, width, height, color, lineWidth) {
+        super()
+        this.x      = x;
+        this.y      = y;
+        this.width  = width;
+        this.height = height;
+        this.color  = color;
+        this.lineWidth = lineWidth;
+        this.draw(); 
+    }
+    
+
+    draw(selected){
+        ctx.beginPath();
+        ctx.moveTo(this.x, this.y);
+        ctx.rect(this.x, this.y, this.width, this.height)
+        ctx.closePath();
+        ctx.strokeStyle = this.color;
+           ctx.fillStyle = this.color;
+        ctx.lineWidth = this.lineWidth;
+         if (selected){
+            ctx.lineWidth = 5
+            ctx.stroke();
+        }
+        ctx.fill();
+    }
+    in(x, y) {
+              return (
+            ((this.width > 0) ? (this.x <= x && x <= (this.x + this.width)) : (this.x + this.width <= x && x <= this.x)) &&
+             ( (this.height>0)? (this.y<= y && y <= (this.y + this.height)):( this.y+this.height<= y && y <= this.y))
+                   )
+    }
+    inBounds(x, y, w, h) { 
+        let areaX = false;
+        let areaY = false;
+        if (this.width > 0) { areaX=(w > 0) ? (this.x >= x && this.x + this.width <= x + w):(this.x >= x + w && this.x + this.width <= x)}
+        else { areaX= ((w > 0) ? (this.x+ this.width >= x && this.x <= x + w):(this.x + this.width>= x + w && this.x <= x)) }
+          if (this.height > 0) { areaY=(h > 0) ? (this.y >= y && this.y + this.height <= y + h):(this.y >= y + h && this.y + this.height <= y)}
+        else { areaY= (h > 0) ? (this.y+ this.height >= y && this.y <= y + h):(this.y + this.height>= y + h && this.y <= y) }
+                    
+        return areaX && areaY;
+                   }
+ 
+} 
+class BoundsRectangle extends Drawable { //смочь скопировать в Rectangle и поменять отприсовку
+    constructor(x, y, width, height, color, lineWidth) {
+        super()
+        this.x      = x;
+        this.y      = y;
+        this.width  = width;
+        this.height = height;
+        this.color  = 'grey';
+        this.lineWidth = 2;
+        this.draw(); 
+    }
+    
+
+    draw(){
+        ctx.beginPath();
+        ctx.moveTo(this.x, this.y);
+        ctx.rect(this.x, this.y, this.width, this.height)
+        ctx.closePath();
+        ctx.strokeStyle = this.color;
+                ctx.lineWidth = this.lineWidth;
+                 ctx.stroke();
+ 
+    }
+       
+
+} 
+
+
+color.onchange = () => {
+    selection.forEach(c => c.color = color.value)
+    Drawable.drawAll(selection)
+}
+
+document.getElementById('delete').onclick = () =>{
+    Drawable.instances = Drawable.instances.filter(item => !selection.includes(item))
+    selection = []
+    Drawable.drawAll()
+}
+
+
+
+
+//new Line(0,0,100,100, "red")
+////new Circle(30,30,10, "red")
+
+////canvas.onmousemove = function(e){
+////}
+
+//undo.onclick = function(){
+    //Drawable.instances.pop()
+    ////Drawable.instances = []
+    //Drawable.drawAll()
+//}

+ 95 - 0
hw-js-18-2-canvas/js/form.js

@@ -0,0 +1,95 @@
+function dateToDateTimeLocal(date) {
+    let myDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
+    console.log(myDate.toISOString().slice(0, -1));
+return myDate.toISOString().slice(0, -1)
+}
+
+dateToDateTimeLocal(new Date);
+
+function Form(el, data, okCallback, cancelCallback) {
+    let formBody = document.createElement('div')
+    let okButton = document.createElement('button')
+    okButton.innerHTML = 'OK'
+
+    let cancelButton = document.createElement('button')
+    cancelButton.innerHTML = 'Cancel'
+let inputCreators = {
+    String(key, value, oninput){
+        let input = document.createElement('input')
+        input.type = 'text'
+        input.placeholder = key
+        input.value       = value
+        input.oninput     = () => oninput(input.value)
+        return input
+    },
+    Boolean(key, value, oninput) {
+       
+        //label for с input type='checkbox'  
+            let input = document.createElement('input')
+        input.type = 'checkbox'
+        input.placeholder = key
+        input.checked       = value
+        input.onchange     = () => oninput(input.checked)
+        return input
+    },
+    Date(key, value, oninput) {
+             let input = document.createElement('input')
+        input.type = 'datetime-local'
+        input.placeholder = key
+        input.value = dateToDateTimeLocal(value);
+        input.oninput     = () => oninput(new Date(input.value))
+        return input
+        //используйте datetime-local
+       
+    },
+    //и др. по вкусу, например textarea для длинных строк
+}
+    formBody.innerHTML = '<h1>тут будет форма после перервы</h1>'
+    for (const [key, value] of Object.entries(data)) {
+        let input = inputCreators[value.constructor.name](key, value, newValue => { data[key] = newValue });
+       
+        // let input = document.createElement('input');
+        formBody.append(input);
+        input.placeholder = key;
+        
+        // input.oninput = () => {
+        //    data[key] = input.value;
+        //         }
+            }
+     
+    if (typeof okCallback === 'function'){
+        formBody.appendChild(okButton);
+        okButton.onclick = (e) => {
+            console.log(this)
+            this.okCallback(e)
+        }
+    }
+
+    if (typeof cancelCallback === 'function'){
+        formBody.appendChild(cancelButton);
+        cancelButton.onclick = cancelCallback
+    }
+
+    el.appendChild(formBody)
+
+
+    this.okCallback     = okCallback
+    this.cancelCallback = cancelCallback
+
+    this.data           = data
+    this.validators     = {}
+}
+
+
+let form = new Form(formContainer, {
+    name: 'Anakin',
+    surname: 'Skywalker',
+    married: true,
+    birthday: new Date((new Date).getTime() - 86400000 * 30*365)
+}, () => console.log('ok'),() => console.log('cancel') )
+form.okCallback = () => console.log('ok2')
+
+form.validators.surname = (value, key, data, input) => value.length > 2 && 
+                                                     value[0].toUpperCase() == value[0] &&
+                                                    !value.includes(' ') ? true : 'Wrong name'
+console.log(form)

+ 19 - 0
hw-js-18-2-canvas/js/table.js

@@ -0,0 +1,19 @@
+// task-table
+let i;
+let j;
+
+const array = [i, j];
+
+let str = "<table>";
+for (i = 1; i <10; i++) {
+    str += "<tr>";
+    for (j = 1; j < 10; j++) {
+        str += "<td style='width:70px'>" + i +"*"+j+"="+ i * j + "</td>";
+    }
+    str += "</tr>";
+}
+str +="</table>";
+document.body.insertAdjacentHTML("afterbegin", str);
+
+
+

+ 6 - 0
hw-js-18-2-canvas/js/task-sum-array.js

@@ -0,0 +1,6 @@
+// task-sum-array
+let arr = [1, 2, 3, 4,10];
+let sum = 0;
+for (let item of arr) { sum = sum+item };
+// console.log(arr.concat(sum));
+console.log([...arr,sum]);