Compare commits
8 Commits
76555d27a4
...
sem2-lab1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64b82e3982 | ||
| 627f74838c | |||
| 4b6d65a946 | |||
| e64b644b33 | |||
| f17768bb8e | |||
| 4cf18c4895 | |||
| c3996ec6c6 | |||
| 4f1c66e838 |
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
**node_modules
|
**node_modules
|
||||||
**to_reform
|
**to_reform
|
||||||
.vscode
|
.vscode
|
||||||
|
current_site/dist
|
||||||
@@ -80,227 +80,4 @@ block variables
|
|||||||
{ id: 'val_temperature', value: 'AverageTemperature', label: 'Average Temperature', checked: true },
|
{ id: 'val_temperature', value: 'AverageTemperature', label: 'Average Temperature', checked: true },
|
||||||
{ id: 'val_humidity', value: 'Humidity', label: 'Average Humidity', checked: false },
|
{ id: 'val_humidity', value: 'Humidity', label: 'Average Humidity', checked: false },
|
||||||
{ id: 'val_battery', value: 'BatteryLevel', label: 'Average BatteryLevel', checked: false }
|
{ id: 'val_battery', value: 'BatteryLevel', label: 'Average BatteryLevel', checked: false }
|
||||||
]
|
]
|
||||||
|
|
||||||
var sensorData = [
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-001',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:41:50Z',
|
|
||||||
temperature: 19.4,
|
|
||||||
tempDifference: -0.2,
|
|
||||||
sensorType: 'Bmp220',
|
|
||||||
batteryLevel: 95,
|
|
||||||
humidity: 38.1,
|
|
||||||
lastCalibrationDate: '2025-07-26'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-003',
|
|
||||||
location: 'Bedroom',
|
|
||||||
timestamp: '2025-10-03T03:42:46Z',
|
|
||||||
temperature: 18.2,
|
|
||||||
tempDifference: -0.1,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 75,
|
|
||||||
humidity: 45.5,
|
|
||||||
lastCalibrationDate: '2025-07-21'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-004',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:43:48Z',
|
|
||||||
temperature: 24.1,
|
|
||||||
tempDifference: 0.1,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 72,
|
|
||||||
humidity: 50.5,
|
|
||||||
lastCalibrationDate: '2025-08-30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-001',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:44:34Z',
|
|
||||||
temperature: 19.1,
|
|
||||||
tempDifference: -0.3,
|
|
||||||
sensorType: 'Bmp220',
|
|
||||||
batteryLevel: 95,
|
|
||||||
humidity: 56.5,
|
|
||||||
lastCalibrationDate: '2025-07-26'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-006',
|
|
||||||
location: 'Kitchen',
|
|
||||||
timestamp: '2025-10-03T03:45:46Z',
|
|
||||||
temperature: 22.8,
|
|
||||||
tempDifference: 0.0,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 80,
|
|
||||||
humidity: 41.4,
|
|
||||||
lastCalibrationDate: '2025-09-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-010',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:46:18Z',
|
|
||||||
temperature: 21.7,
|
|
||||||
tempDifference: 0.5,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 79,
|
|
||||||
humidity: 59.8,
|
|
||||||
lastCalibrationDate: '2025-08-07'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-006',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:47:05Z',
|
|
||||||
temperature: 22.7,
|
|
||||||
tempDifference: -0.1,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 80,
|
|
||||||
humidity: 36.1,
|
|
||||||
lastCalibrationDate: '2025-09-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-006',
|
|
||||||
location: 'Bedroom',
|
|
||||||
timestamp: '2025-10-03T03:48:13Z',
|
|
||||||
temperature: 22.9,
|
|
||||||
tempDifference: 0.2,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 80,
|
|
||||||
humidity: 56.1,
|
|
||||||
lastCalibrationDate: '2025-09-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-005',
|
|
||||||
location: 'Living Room',
|
|
||||||
timestamp: '2025-10-03T03:49:03Z',
|
|
||||||
temperature: 24.4,
|
|
||||||
tempDifference: 0.4,
|
|
||||||
sensorType: 'Bmp180',
|
|
||||||
batteryLevel: 91,
|
|
||||||
humidity: 30.1,
|
|
||||||
lastCalibrationDate: '2025-08-07'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-001',
|
|
||||||
location: 'Kitchen',
|
|
||||||
timestamp: '2025-10-03T03:50:24Z',
|
|
||||||
temperature: 19.1,
|
|
||||||
tempDifference: 0.0,
|
|
||||||
sensorType: 'Bmp220',
|
|
||||||
batteryLevel: 95,
|
|
||||||
humidity: 64.8,
|
|
||||||
lastCalibrationDate: '2025-07-26'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-008',
|
|
||||||
location: 'Living Room',
|
|
||||||
timestamp: '2025-10-03T03:51:12Z',
|
|
||||||
temperature: 21.9,
|
|
||||||
tempDifference: -0.1,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 92,
|
|
||||||
humidity: 36.0,
|
|
||||||
lastCalibrationDate: '2025-08-02'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-009',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:51:46Z',
|
|
||||||
temperature: 22.3,
|
|
||||||
tempDifference: 0.0,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 76,
|
|
||||||
humidity: 74.0,
|
|
||||||
lastCalibrationDate: '2025-09-17'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-003',
|
|
||||||
location: 'Kitchen',
|
|
||||||
timestamp: '2025-10-03T03:52:23Z',
|
|
||||||
temperature: 18.4,
|
|
||||||
tempDifference: 0.2,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 75,
|
|
||||||
humidity: 45.2,
|
|
||||||
lastCalibrationDate: '2025-07-21'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-010',
|
|
||||||
location: 'Living Room',
|
|
||||||
timestamp: '2025-10-03T03:53:44Z',
|
|
||||||
temperature: 21.8,
|
|
||||||
tempDifference: 0.1,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 79,
|
|
||||||
humidity: 36.3,
|
|
||||||
lastCalibrationDate: '2025-08-07'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-001',
|
|
||||||
location: 'Kitchen',
|
|
||||||
timestamp: '2025-10-03T03:54:46Z',
|
|
||||||
temperature: 18.7,
|
|
||||||
tempDifference: -0.4,
|
|
||||||
sensorType: 'Bmp220',
|
|
||||||
batteryLevel: 95,
|
|
||||||
humidity: 44.3,
|
|
||||||
lastCalibrationDate: '2025-07-26'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-006',
|
|
||||||
location: 'Bedroom',
|
|
||||||
timestamp: '2025-10-03T03:55:24Z',
|
|
||||||
temperature: 23.3,
|
|
||||||
tempDifference: 0.4,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 80,
|
|
||||||
humidity: 55.9,
|
|
||||||
lastCalibrationDate: '2025-09-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-002',
|
|
||||||
location: 'Kitchen',
|
|
||||||
timestamp: '2025-10-03T03:55:57Z',
|
|
||||||
temperature: 17.7,
|
|
||||||
tempDifference: -0.5,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 93,
|
|
||||||
humidity: 70.2,
|
|
||||||
lastCalibrationDate: '2025-08-30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-002',
|
|
||||||
location: 'Balcony',
|
|
||||||
timestamp: '2025-10-03T03:57:05Z',
|
|
||||||
temperature: 17.4,
|
|
||||||
tempDifference: -0.3,
|
|
||||||
sensorType: 'Scd30(Internal)',
|
|
||||||
batteryLevel: 93,
|
|
||||||
humidity: 66.7,
|
|
||||||
lastCalibrationDate: '2025-08-30'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-009',
|
|
||||||
location: 'Living Room',
|
|
||||||
timestamp: '2025-10-03T03:58:28Z',
|
|
||||||
temperature: 22.6,
|
|
||||||
tempDifference: 0.3,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 76,
|
|
||||||
humidity: 56.4,
|
|
||||||
lastCalibrationDate: '2025-09-17'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sensorId: 'TEMP-004',
|
|
||||||
location: 'Bedroom',
|
|
||||||
timestamp: '2025-10-03T03:59:51Z',
|
|
||||||
temperature: 24.3,
|
|
||||||
tempDifference: 0.2,
|
|
||||||
sensorType: 'Xiaomi',
|
|
||||||
batteryLevel: 72,
|
|
||||||
humidity: 47.3,
|
|
||||||
lastCalibrationDate: '2025-08-30'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -27,98 +27,52 @@ mixin galleryMixin()
|
|||||||
mixin filtersMixin()
|
mixin filtersMixin()
|
||||||
details.filters
|
details.filters
|
||||||
summary.filters__summary Filters
|
summary.filters__summary Filters
|
||||||
form.filters__form
|
form(id='filters').filters__form
|
||||||
fieldset.filters__fieldset
|
p
|
||||||
.filter-group
|
label(for='type') Type:
|
||||||
i.filter-group__label
|
input(type='text' id='type')
|
||||||
b Location
|
p
|
||||||
br
|
label(for='name') Name:
|
||||||
.filter-group__items
|
input(type='text' id='name')
|
||||||
each filter in locationFilters
|
p
|
||||||
input.filter-group__checkbox(type="checkbox" id=filter.id name="location" value=filter.value)
|
label(for='manufacturer') Manufacturer name:
|
||||||
span #{filter.label}
|
input(type='text' id='manufacturer')
|
||||||
br
|
p
|
||||||
|
label(for='sizeFrom') Size:
|
||||||
.filter-group
|
| from
|
||||||
i.filter-group__label
|
input(type='number' id='sizeFrom')
|
||||||
b Sensor Type
|
| to
|
||||||
br
|
input(type='number' id='sizeTo')
|
||||||
.filter-group__items
|
p
|
||||||
each filter in sensorTypeFilters
|
label(for='releaseDateFrom') Release Date:
|
||||||
input.filter-group__checkbox(type="checkbox" id=filter.id name="sensor_type" value=filter.value)
|
| from
|
||||||
span #{filter.label}
|
input(type='number' id='releaseDateFrom')
|
||||||
br
|
| to
|
||||||
|
input(type='number' id='releaseDateTo')
|
||||||
.filter-group
|
p
|
||||||
i.filter-group__label
|
label(for='priceFrom') Release Date:
|
||||||
b Numeric Values
|
| from
|
||||||
br
|
input(type='number' id='priceFrom')
|
||||||
.filter-group__range
|
| to
|
||||||
span Temperature from:
|
input(type='number' id='priceTo')
|
||||||
input.filter-group__input(type="number" name="temp_min" placeholder="20")
|
input.filter-group__button--apply(type='button' id='applyFiltersButton' value="Apply Filters")
|
||||||
span to:
|
input.filter-group__button--reset(type='button' id='clearFiltersButton' value="Reset Filters")
|
||||||
input.filter-group__input(type="number" name="temp_max" placeholder="30")
|
|
||||||
br
|
|
||||||
span Humidity from:
|
|
||||||
input.filter-group__input(type="number" name="humidity_min" placeholder="40")
|
|
||||||
span to:
|
|
||||||
input.filter-group__input(type="number" name="humidity_max" placeholder="60")
|
|
||||||
br
|
|
||||||
|
|
||||||
.filter-group
|
|
||||||
i.filter-group__label
|
|
||||||
b Device Specific Filters
|
|
||||||
br
|
|
||||||
.filter-group__items
|
|
||||||
span Sensor Name:
|
|
||||||
input.filter-group__input(type="text" name="sensor_name")
|
|
||||||
br
|
|
||||||
span Days Since Calibration (max):
|
|
||||||
input.filter-group__input(type="number" name="calibration_days" placeholder="30")
|
|
||||||
br
|
|
||||||
|
|
||||||
.filter-group
|
|
||||||
i.filter-group__label
|
|
||||||
b Measurement Period
|
|
||||||
br
|
|
||||||
.filter-group__dates
|
|
||||||
span From:
|
|
||||||
input.filter-group__input(type="date" name="date_from")
|
|
||||||
br
|
|
||||||
span To:
|
|
||||||
input.filter-group__input(type="date" name="date_to")
|
|
||||||
br
|
|
||||||
|
|
||||||
.filter-group__buttons
|
|
||||||
input.filter-group__button.filter-group__button--submit(type="submit" value="Apply Filters")
|
|
||||||
input.filter-group__button.filter-group__button--reset(type="reset" value="Reset Filters")
|
|
||||||
|
|
||||||
mixin sortLevel(level)
|
mixin sortLevel(level)
|
||||||
.sort-level
|
p
|
||||||
i.sort-level__label
|
select.sort-level__select(id=`sort_${level}`)
|
||||||
b Sort Level #{level}
|
| ascending?
|
||||||
br
|
input(type="checkbox", id=`sort_${level}Desc`)
|
||||||
label.sort-level__field-label(for=`sort${level}`) Field:
|
|
||||||
select.sort-level__select(id=`sort${level}` name=`sort${level}`)
|
|
||||||
each option in sortOptions
|
|
||||||
option(value=option.value) #{option.label}
|
|
||||||
label.sort-level__reverse-label(for=`sort${level}_reverse`)
|
|
||||||
input.sort-level__checkbox(type="checkbox" id=`sort${level}_reverse` name=`sort${level}_reverse`)
|
|
||||||
span Reverse
|
|
||||||
br
|
|
||||||
if level === '3'
|
|
||||||
input.sort-level__submit(type="submit" value="Sort")
|
|
||||||
else
|
|
||||||
br
|
|
||||||
|
|
||||||
mixin sortingMixin()
|
mixin sortingMixin()
|
||||||
- const sortLevels = ['1', '2', '3'];
|
- const sortLevels = ['1', '2', '3'];
|
||||||
details.sorting
|
details.sorting
|
||||||
summary.sorting__summary Sortings
|
summary.sorting__summary Sort
|
||||||
form.sorting__form
|
form(id='sorting')
|
||||||
fieldset.sorting__fieldset
|
each level in sortLevels
|
||||||
each level in sortLevels
|
+sortLevel(level)
|
||||||
+sortLevel(level)
|
input.sort-group__button--apply(type='button' id='applySortButton' value="Apply Sort")
|
||||||
|
input.sort-group__button--reset(type='button' id='resetSortButton' value="Reset Sort")
|
||||||
|
|
||||||
|
|
||||||
mixin graphXOption(option)
|
mixin graphXOption(option)
|
||||||
if option.checked
|
if option.checked
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
include ../components/mixins.pug
|
include ../components/mixins.pug
|
||||||
include ../components/data.pug
|
|
||||||
|
|
||||||
head
|
head
|
||||||
title table
|
title table
|
||||||
|
script(src='./table/data.js')
|
||||||
|
script(src='./table/filter.js')
|
||||||
|
script(src='./table/main.js')
|
||||||
|
script(src='./table/sort.js')
|
||||||
|
script(src='./table/table.js')
|
||||||
|
|
||||||
body
|
body
|
||||||
+navbarMixin("Table")
|
+navbarMixin("Table")
|
||||||
|
|
||||||
|
.table-controls
|
||||||
|
+filtersMixin()
|
||||||
|
+sortingMixin()
|
||||||
|
+graphMixin()
|
||||||
|
|
||||||
.table-controls
|
table(id='list')
|
||||||
+filtersMixin()
|
|
||||||
+sortingMixin()
|
|
||||||
+graphMixin()
|
|
||||||
|
|
||||||
table.table
|
|
||||||
tbody.table-content
|
|
||||||
each dataItem,index in sensorData
|
|
||||||
if index == 0
|
|
||||||
tr.table-content__table-header
|
|
||||||
each value,header in dataItem
|
|
||||||
th #{header}
|
|
||||||
tr.table-content__table-row
|
|
||||||
each value,header in dataItem
|
|
||||||
td #{value}
|
|
||||||
|
|
||||||
|
|||||||
68
current_site/src/pages/table/data.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
let ram_sticks = [
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1600-4GB-A1", "size": 4, "maker": "Kingston", "release": "2014-03", "price": 18, },
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1600-8GB-A2", "size": 8, "maker": "Corsair", "release": "2015-06", "price": 26, },
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1866-8GB-A3", "size": 8, "maker": "G.Skill", "release": "2016-02", "price": 29, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-2133-8GB-B1", "size": 8, "maker": "Crucial", "release": "2017-01", "price": 24, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-2400-8GB-B2", "size": 8, "maker": "Kingston", "release": "2017-09", "price": 27, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-2666-16GB-B3", "size": 16, "maker": "Corsair", "release": "2018-04", "price": 48, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3000-16GB-B4", "size": 16, "maker": "G.Skill", "release": "2018-11", "price": 52, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-16GB-B5", "size": 16, "maker": "HyperX", "release": "2019-03", "price": 55, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-32GB-B6", "size": 32, "maker": "Crucial", "release": "2019-08", "price": 92, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3600-32GB-B7", "size": 32, "maker": "Corsair", "release": "2020-02", "price": 99, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-4800-16GB-C1", "size": 16, "maker": "Kingston", "release": "2021-01", "price": 78, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-5200-16GB-C2", "size": 16, "maker": "Corsair", "release": "2021-06", "price": 84, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-5600-32GB-C3", "size": 32, "maker": "G.Skill", "release": "2022-02", "price": 145, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-32GB-C4", "size": 32, "maker": "Crucial", "release": "2022-07", "price": 158, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6400-32GB-C5", "size": 32, "maker": "Corsair", "release": "2023-01", "price": 172, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6600-64GB-C6", "size": 64, "maker": "Kingston", "release": "2023-05", "price": 310, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6800-64GB-C7", "size": 64, "maker": "G.Skill", "release": "2023-09", "price": 329, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-7200-64GB-C8", "size": 64, "maker": "Corsair", "release": "2024-02", "price": 355, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-7600-96GB-C9", "size": 96, "maker": "Crucial", "release": "2024-06", "price": 520, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-8000-96GB-C10", "size": 96, "maker": "Kingston", "release": "2024-10", "price": 560, },
|
||||||
|
{ "type": "LPDDR4", "name": "LP4-3200-8GB-D1", "size": 8, "maker": "Samsung", "release": "2019-01", "price": 34, },
|
||||||
|
{ "type": "LPDDR4", "name": "LP4-4266-8GB-D2", "size": 8, "maker": "Micron", "release": "2019-07", "price": 39, },
|
||||||
|
{ "type": "LPDDR5", "name": "LP5-5500-12GB-D3", "size": 12, "maker": "Samsung", "release": "2020-03", "price": 58, },
|
||||||
|
{ "type": "LPDDR5", "name": "LP5-6400-16GB-D4", "size": 16, "maker": "SKHynix", "release": "2021-01", "price": 74, },
|
||||||
|
{ "type": "LPDDR5X", "name": "LP5X-7500-24GB-D5", "size": 24, "maker": "Micron", "release": "2022-05", "price": 118, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-2666-8GB-E1", "size": 8, "maker": "Patriot", "release": "2018-05", "price": 25, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3000-8GB-E2", "size": 8, "maker": "ADATA", "release": "2018-09", "price": 28, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-8GB-E3", "size": 8, "maker": "TeamGroup", "release": "2019-04", "price": 30, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3600-16GB-E4", "size": 16, "maker": "ADATA", "release": "2020-01", "price": 53, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-4000-16GB-E5", "size": 16, "maker": "Patriot", "release": "2020-06", "price": 61, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-5200-8GB-F1", "size": 8, "maker": "TeamGroup", "release": "2021-03", "price": 52, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-5600-16GB-F2", "size": 16, "maker": "ADATA", "release": "2021-10", "price": 88, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-16GB-F3", "size": 16, "maker": "Patriot", "release": "2022-03", "price": 95, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6400-32GB-F4", "size": 32, "maker": "TeamGroup", "release": "2022-09", "price": 168, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-7200-32GB-F5", "size": 32, "maker": "ADATA", "release": "2023-04", "price": 185, },
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1333-4GB-G1", "size": 4, "maker": "Samsung", "release": "2013-02", "price": 15, },
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1600-4GB-G2", "size": 4, "maker": "Micron", "release": "2014-08", "price": 17, },
|
||||||
|
{ "type": "DDR3", "name": "DDR3-1866-8GB-G3", "size": 8, "maker": "Samsung", "release": "2015-11", "price": 28, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-8400-128GB-H1", "size": 128, "maker": "Corsair", "release": "2025-01", "price": 890, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-8800-128GB-H2", "size": 128, "maker": "G.Skill", "release": "2025-03", "price": 940, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-9200-128GB-H3", "size": 128, "maker": "Kingston", "release": "2025-06", "price": 990, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X1", "size": 48, "maker": "Corsair", "release": "2024-01", "price": 210, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X2", "size": 48, "maker": "Kingston", "release": "2024-02", "price": 215, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X3", "size": 48, "maker": "G.Skill", "release": "2024-03", "price": 218, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X4", "size": 48, "maker": "ADATA", "release": "2024-04", "price": 222, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X5", "size": 48, "maker": "Patriot", "release": "2024-05", "price": 225, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X6", "size": 48, "maker": "TeamGroup", "release": "2024-06", "price": 228, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X7", "size": 48, "maker": "Crucial", "release": "2024-07", "price": 230, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X8", "size": 48, "maker": "Samsung", "release": "2024-08", "price": 235, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X9", "size": 48, "maker": "Micron", "release": "2024-09", "price": 238, },
|
||||||
|
{ "type": "DDR5", "name": "DDR5-6000-48GB-X10", "size": 48, "maker": "SKHynix", "release": "2024-10", "price": 240, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y1", "size": 64, "maker": "Corsair", "release": "2021-01", "price": 180, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y2", "size": 64, "maker": "Kingston", "release": "2021-02", "price": 182, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y3", "size": 64, "maker": "G.Skill", "release": "2021-03", "price": 185, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y4", "size": 64, "maker": "ADATA", "release": "2021-04", "price": 188, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y5", "size": 64, "maker": "Patriot", "release": "2021-05", "price": 190, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y6", "size": 64, "maker": "TeamGroup", "release": "2021-06", "price": 192, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y7", "size": 64, "maker": "Crucial", "release": "2021-07", "price": 195, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y8", "size": 64, "maker": "Samsung", "release": "2021-08", "price": 198, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y9", "size": 64, "maker": "Micron", "release": "2021-09", "price": 200, },
|
||||||
|
{ "type": "DDR4", "name": "DDR4-3200-64GB-Y10", "size": 64, "maker": "SKHynix", "release": "2021-10", "price": 205, }
|
||||||
|
|
||||||
|
]
|
||||||
|
ram_sticks = ram_sticks.map((x) => ({
|
||||||
|
...x,
|
||||||
|
release: Number(x.release.split("-")[0]),
|
||||||
|
}))
|
||||||
92
current_site/src/pages/table/filter.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const correspond = {
|
||||||
|
"type": "type",
|
||||||
|
"name": "name",
|
||||||
|
"maker": "manufacturer",
|
||||||
|
"size": ["sizeFrom", "sizeTo"],
|
||||||
|
"release": ["releaseDateFrom", "releaseDateTo"],
|
||||||
|
"price": ["priceFrom", "priceTo"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Структура возвращаемого ассоциативного массива:
|
||||||
|
{
|
||||||
|
input_id: input_value,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const dataFilter = (dataForm) => {
|
||||||
|
let dictFilter = {};
|
||||||
|
|
||||||
|
// перебираем все элементы формы с фильтрами
|
||||||
|
for (const item of dataForm.elements) {
|
||||||
|
|
||||||
|
// получаем значение элемента
|
||||||
|
let valInput = item.value;
|
||||||
|
|
||||||
|
// если поле типа text - приводим его значение к нижнему регистру
|
||||||
|
if (item.type === "text") {
|
||||||
|
valInput = valInput.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === "number") {
|
||||||
|
if (valInput === "") {
|
||||||
|
if (item.id.includes("From")) {
|
||||||
|
valInput = -Infinity;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
valInput = Infinity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
valInput = Number(valInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// формируем очередной элемент ассоциативного массива
|
||||||
|
dictFilter[item.id] = valInput;
|
||||||
|
}
|
||||||
|
return dictFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// фильтрация таблицы
|
||||||
|
const filterTable = (data, idTable, dataForm) => {
|
||||||
|
|
||||||
|
// получаем данные из полей формы
|
||||||
|
const datafilter = dataFilter(dataForm);
|
||||||
|
|
||||||
|
// выбираем данные соответствующие фильтру и формируем таблицу из них
|
||||||
|
let tableFilter = data.filter(item => {
|
||||||
|
|
||||||
|
/* в этой переменной будут "накапливаться" результаты сравнения данных
|
||||||
|
с параметрами фильтра */
|
||||||
|
let result = true;
|
||||||
|
|
||||||
|
// строка соответствует фильтру, если сравнение всех значения из input
|
||||||
|
// со значением ячейки очередной строки - истина
|
||||||
|
Object.entries(item).map(([key, val]) => {
|
||||||
|
|
||||||
|
// текстовые поля проверяем на вхождение
|
||||||
|
if (typeof val == 'string') {
|
||||||
|
result &&= val.toLowerCase().includes(datafilter[correspond[key]])
|
||||||
|
}
|
||||||
|
|
||||||
|
// САМОСТОЯТЕЛЬНО проверить числовые поля на принадлежность интервалу
|
||||||
|
if (typeof val == 'number') {
|
||||||
|
result &&= datafilter[correspond[key][0]] <= Number(val) && datafilter[correspond[key][1]] >= Number(val)
|
||||||
|
}
|
||||||
|
// console.log(result, key, )
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// САМОСТОЯТЕЛЬНО вызвать функцию, которая удаляет все строки таблицы с id=idTable
|
||||||
|
clearTable(idTable);
|
||||||
|
// показать на странице таблицу с отфильтрованными строками
|
||||||
|
createTable(tableFilter, idTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFilters = (form) => {
|
||||||
|
form.querySelectorAll(`& input[type="text"],& input[type="number"]`).forEach((elem) => elem.value = null)
|
||||||
|
}
|
||||||
122
current_site/src/pages/table/main.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
doInit()
|
||||||
|
})
|
||||||
|
|
||||||
|
function doInit() {
|
||||||
|
createTable(ram_sticks, 'list');
|
||||||
|
const clearFiltersButton = document.getElementById("clearFiltersButton");
|
||||||
|
clearFiltersButton.addEventListener("click", (event) => {
|
||||||
|
clearFilters(filters);
|
||||||
|
createTable(ram_sticks, 'list');
|
||||||
|
sortTable('list', sorting)
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyFiltersButton = document.getElementById("applyFiltersButton");
|
||||||
|
applyFiltersButton.addEventListener("click", (event) => {
|
||||||
|
filterTable(ram_sticks, 'list', filters)
|
||||||
|
sortTable('list', sorting)
|
||||||
|
})
|
||||||
|
|
||||||
|
setSortSelects(ram_sticks[0], sorting)
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
for (const elem of sorting.querySelectorAll(`& select`)) {
|
||||||
|
const counter = i;
|
||||||
|
elem.addEventListener("change", () => {
|
||||||
|
updateOptions(counter, sorting)
|
||||||
|
})
|
||||||
|
i+=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sorting.applySortButton.addEventListener("click", () => {
|
||||||
|
clearTable('list');
|
||||||
|
filterTable(ram_sticks, 'list', filters)
|
||||||
|
sortTable('list', sorting)
|
||||||
|
});
|
||||||
|
sorting.resetSortButton.addEventListener("click", () => {
|
||||||
|
resetSort(sorting);
|
||||||
|
clearTable('list');
|
||||||
|
filterTable(ram_sticks, 'list', filters)
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const createOption = (str, val) => {
|
||||||
|
let item = document.createElement('option');
|
||||||
|
item.text = str;
|
||||||
|
item.value = val;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
const resetSortSelect = (sortSelect) => {
|
||||||
|
sortSelect.querySelectorAll(`& option`).forEach((elem) => {
|
||||||
|
elem.parentElement.removeChild(elem)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const all_fields = Object.keys(ram_sticks[0]);
|
||||||
|
|
||||||
|
const setSortSelect = (arr, sortSelect) => {
|
||||||
|
|
||||||
|
// создаем OPTION Нет и добавляем ее в SELECT
|
||||||
|
sortSelect.append(createOption('none', 0));
|
||||||
|
// перебираем массив со значениями опций
|
||||||
|
arr.forEach((item, index) => {
|
||||||
|
sortSelect.append(createOption(item, all_fields.indexOf(item) + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const updateOptions = (current_index, sort_form) => {
|
||||||
|
const elem_collection = sort_form.querySelectorAll(`& select`);
|
||||||
|
let used_options_list = []
|
||||||
|
let i = 0;
|
||||||
|
for (let elem_ of elem_collection) {
|
||||||
|
if (i++ < current_index) {
|
||||||
|
used_options_list.push(all_fields[elem_.value-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current_index >= elem_collection.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const elem = elem_collection[current_index]
|
||||||
|
const available_options = all_fields.filter((x) => !used_options_list.includes(x));
|
||||||
|
elem.disabled = available_options.length == 0;
|
||||||
|
if(current_index>0){
|
||||||
|
elem.disabled |= elem_collection[current_index-1].value==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(elem.disabled){
|
||||||
|
elem.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_val = 0;
|
||||||
|
if (!used_options_list.includes(all_fields[elem.value - 1])) {
|
||||||
|
save_val = elem.value;
|
||||||
|
}
|
||||||
|
resetSortSelect(elem)
|
||||||
|
setSortSelect(available_options, elem)
|
||||||
|
|
||||||
|
elem.value = save_val;
|
||||||
|
|
||||||
|
|
||||||
|
updateOptions(current_index + 1, sort_form)
|
||||||
|
|
||||||
|
}
|
||||||
|
// формируем поля со списком для многоуровневой сортировки
|
||||||
|
const setSortSelects = (data, dataForm) => {
|
||||||
|
const head = Object.keys(data);
|
||||||
|
const allSelect = dataForm.getElementsByTagName('select');
|
||||||
|
|
||||||
|
for (const item of dataForm.elements) {
|
||||||
|
setSortSelect(head, item);
|
||||||
|
}
|
||||||
|
let skipped = false
|
||||||
|
for (const elem of allSelect) {
|
||||||
|
if (!skipped) {
|
||||||
|
skipped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elem.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSort = (form) => {
|
||||||
|
form.querySelectorAll(`& select`).forEach((curSelect, index) => { if (index != 0) { curSelect.disabled = true } curSelect.value = 0 })
|
||||||
|
}
|
||||||
87
current_site/src/pages/table/sort.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*формируем массив для сортировки по двум уровням вида
|
||||||
|
[
|
||||||
|
{column: номер столбца, по которому осуществляется сортировка,
|
||||||
|
direction: порядок сортировки (true по убыванию, false по возрастанию)
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
const createSortArr = (data) => {
|
||||||
|
let sortArr = [];
|
||||||
|
|
||||||
|
const sortSelects = data.getElementsByTagName('select');
|
||||||
|
|
||||||
|
for (const item of sortSelects) {
|
||||||
|
// получаем номер выбранной опции
|
||||||
|
const keySort = item.value;
|
||||||
|
// в случае, если выбрана опция Нет, заканчиваем формировать массив
|
||||||
|
if (keySort == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// получаем порядок сортировки очередного уровня
|
||||||
|
// имя флажка сформировано как имя поля SELECT и слова Desc
|
||||||
|
const desc = document.getElementById(item.id + 'Desc').checked;
|
||||||
|
// очередной элемент массива - по какому столбцу и в каком порядке сортировать
|
||||||
|
sortArr.push(
|
||||||
|
{
|
||||||
|
column: keySort - 1,
|
||||||
|
direction: desc
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sortArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberColumns = [2, 4, 5]
|
||||||
|
|
||||||
|
const sortTable = (idTable, formData) => {
|
||||||
|
|
||||||
|
// формируем управляющий массив для сортировки
|
||||||
|
const sortArr = createSortArr(formData);
|
||||||
|
|
||||||
|
// сортировать таблицу не нужно, во всех полях выбрана опция Нет
|
||||||
|
if (sortArr.length === 0) {
|
||||||
|
// clearTable(idTable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
//находим нужную таблицу
|
||||||
|
let table = document.getElementById(idTable);
|
||||||
|
|
||||||
|
// преобразуем строки таблицы в массив
|
||||||
|
let rowData = Array.from(table.rows);
|
||||||
|
|
||||||
|
// удаляем элемент с заголовками таблицы
|
||||||
|
const headerRow = rowData.shift();
|
||||||
|
|
||||||
|
//сортируем данные по всем уровням сортировки
|
||||||
|
rowData.sort((first, second) => {
|
||||||
|
for (let { column, direction } of sortArr) {
|
||||||
|
const firstCell = first.cells[column].innerHTML;
|
||||||
|
const secondCell = second.cells[column].innerHTML;
|
||||||
|
|
||||||
|
// используем localeCompare для корректного сравнения
|
||||||
|
let comparison = null;
|
||||||
|
if (numberColumns.includes(column)) {
|
||||||
|
comparison = Number(firstCell) - Number(secondCell);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comparison = firstCell.localeCompare(secondCell);
|
||||||
|
}
|
||||||
|
// учитываем направление сортировки
|
||||||
|
if (comparison !== 0) {
|
||||||
|
return (direction ? -comparison : comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
clearTable(idTable)
|
||||||
|
//выводим отсортированную таблицу на страницу
|
||||||
|
|
||||||
|
let tbody = document.createElement('tbody');
|
||||||
|
rowData.forEach(item => {
|
||||||
|
tbody.append(item);
|
||||||
|
});
|
||||||
|
table.append(tbody);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
51
current_site/src/pages/table/table.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const createTable = (data, idTable) => {
|
||||||
|
const table = document.getElementById(idTable);
|
||||||
|
if (data.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const header = Object.keys(data[0]);
|
||||||
|
|
||||||
|
/* создание шапки таблицы */
|
||||||
|
if (!table.firstElementChild) {
|
||||||
|
const headerRow = createHeaderRow(header);
|
||||||
|
table.append(headerRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* создание тела таблицы */
|
||||||
|
const bodyRows = createBodyRows(data);
|
||||||
|
table.append(bodyRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearTable = (idTable) => {
|
||||||
|
const table = document.getElementById(idTable);
|
||||||
|
if (table.children.length > 1) {
|
||||||
|
table.removeChild(table.children[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createHeaderRow = (headers) => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
headers.forEach(header => {
|
||||||
|
const th = document.createElement('th');
|
||||||
|
|
||||||
|
th.innerHTML = header.slice(0,1).toUpperCase()+header.slice(1);
|
||||||
|
tr.append(th);
|
||||||
|
});
|
||||||
|
const thead = document.createElement('thead')
|
||||||
|
thead.appendChild(tr)
|
||||||
|
return thead;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBodyRows = (rows) => {
|
||||||
|
const body = document.createElement('tbody');
|
||||||
|
rows.forEach(row => {
|
||||||
|
const rowElement = document.createElement('tr');
|
||||||
|
for (let key in row) {
|
||||||
|
const td = document.createElement('td');
|
||||||
|
td.innerHTML = row[key];
|
||||||
|
rowElement.appendChild(td);
|
||||||
|
}
|
||||||
|
body.appendChild(rowElement);
|
||||||
|
})
|
||||||
|
return body;
|
||||||
|
}
|
||||||
@@ -59,11 +59,24 @@ module.exports = {
|
|||||||
onStart: {
|
onStart: {
|
||||||
delete: ["dist"],
|
delete: ["dist"],
|
||||||
},
|
},
|
||||||
|
onEnd: {
|
||||||
|
copy: [
|
||||||
|
{
|
||||||
|
source: path.join(__dirname, "src", "pages", "table", "*.js"),
|
||||||
|
destination: path.join(__dirname, "dist", "table"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
watchFiles: path.join(__dirname, "src"),
|
watchFiles: path.join(__dirname, "src"),
|
||||||
port: 9000,
|
port: 9000,
|
||||||
|
static: {
|
||||||
|
directory: path.join(__dirname, "src", "pages"),
|
||||||
|
publicPath: "/",
|
||||||
|
watch: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
doInit()
|
|
||||||
})
|
|
||||||
|
|
||||||
function doInit() {
|
|
||||||
createTable(buildings, 'list');
|
|
||||||
const clearFiltersButton = document.getElementById("clearFiltersButton")
|
|
||||||
clearFiltersButton.addEventListener("click", (event) => {
|
|
||||||
clearTable("list")
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const applyFiltersButton = document.getElementById("applyFiltersButton")
|
|
||||||
applyFiltersButton.addEventListener("click", (event) => {
|
|
||||||
filterTable()
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Фильтры и сортировка</title>
|
|
||||||
<link rel="stylesheet" href="CSS/style.css">
|
|
||||||
<script src="JavaScript/data.js"></script>
|
|
||||||
<script src="JavaScript/main.js"></script>
|
|
||||||
<script src="JavaScript/table.js"></script>
|
|
||||||
<script src="JavaScript/filter.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<form id="filter">
|
|
||||||
<p>
|
|
||||||
<label for="structure">Название:</label>
|
|
||||||
<input type="text" id="structure">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="category">Тип:</label>
|
|
||||||
<input type="text" id="category">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="country">Страна:</label>
|
|
||||||
<input type="text" id="country">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="city">Город:</label>
|
|
||||||
<input type="text" id="city">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="yearFrom">Год: </label>
|
|
||||||
от <input type="number" id="yearFrom">
|
|
||||||
до <input type="number" id="yearTo">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="heightFrom">Высота:</label>
|
|
||||||
от <input type="number" id="heightFrom">
|
|
||||||
до <input type="number" id="heightTo">
|
|
||||||
</p>
|
|
||||||
<input id="applyFiltersButton" type="button" value="Найти">
|
|
||||||
<input id="clearFiltersButton" type="button" value="Очистить фильтры">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<table id="list">
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
32
lab1/temp.js
@@ -1,32 +0,0 @@
|
|||||||
// const arr = [1, 2, 3, 4, 5];
|
|
||||||
// const replaced = arr
|
|
||||||
// replaced.splice(2,1,100)
|
|
||||||
// const removed = [...replaced];
|
|
||||||
// removed.splice(3,1);
|
|
||||||
// const updated = [...removed];
|
|
||||||
// updated.splice(3,0,200);
|
|
||||||
// console.log(updated);
|
|
||||||
|
|
||||||
const arr = [2, 2, 4, 1, 6, 12]
|
|
||||||
|
|
||||||
// const ret = arr.map((val, i, array) => {
|
|
||||||
|
|
||||||
// return array.slice(Math.max(0, i - 1), Math.min(array.length - 1, i + 2)) /
|
|
||||||
// (Math.max(0, i - 1)-Math.min(array.length - 1, i + 1))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// console.log(ret)
|
|
||||||
|
|
||||||
// let min = arr.reduce((acc,x)=>x<acc?x:acc);
|
|
||||||
// console.log(min)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function groupArr(func,...args){
|
|
||||||
return args.reduce(func)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const users =
|
|
||||||
@@ -19,66 +19,73 @@ const dataFilter = (dataForm) => {
|
|||||||
|
|
||||||
// перебираем все элементы формы с фильтрами
|
// перебираем все элементы формы с фильтрами
|
||||||
for (const item of dataForm.elements) {
|
for (const item of dataForm.elements) {
|
||||||
|
|
||||||
// получаем значение элемента
|
// получаем значение элемента
|
||||||
let valInput = item.value;
|
let valInput = item.value;
|
||||||
|
|
||||||
// если поле типа text - приводим его значение к нижнему регистру
|
// если поле типа text - приводим его значение к нижнему регистру
|
||||||
if (item.type === "text") {
|
if (item.type === "text") {
|
||||||
valInput = valInput.toLowerCase();
|
valInput = valInput.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === "number"){
|
if (item.type === "number") {
|
||||||
if (valInput.value === ""){
|
if (valInput === "") {
|
||||||
if(valInput.name.includes("From")){
|
if (item.id.includes("From")) {
|
||||||
valInput = -Infinity;
|
valInput = -Infinity;
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
valInput = Infinity;
|
valInput = Infinity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
valInput = Number(valInput);
|
else {
|
||||||
|
valInput = Number(valInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// формируем очередной элемент ассоциативного массива
|
// формируем очередной элемент ассоциативного массива
|
||||||
dictFilter[item.id] = valInput;
|
dictFilter[item.id] = valInput;
|
||||||
}
|
}
|
||||||
return dictFilter;
|
return dictFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// фильтрация таблицы
|
// фильтрация таблицы
|
||||||
const filterTable = (data, idTable, dataForm) =>{
|
const filterTable = (data, idTable, dataForm) => {
|
||||||
|
|
||||||
// получаем данные из полей формы
|
// получаем данные из полей формы
|
||||||
const datafilter = dataFilter(dataForm);
|
const datafilter = dataFilter(dataForm);
|
||||||
|
|
||||||
// выбираем данные соответствующие фильтру и формируем таблицу из них
|
// выбираем данные соответствующие фильтру и формируем таблицу из них
|
||||||
let tableFilter = data.filter(item => {
|
let tableFilter = data.filter(item => {
|
||||||
|
|
||||||
/* в этой переменной будут "накапливаться" результаты сравнения данных
|
/* в этой переменной будут "накапливаться" результаты сравнения данных
|
||||||
с параметрами фильтра */
|
с параметрами фильтра */
|
||||||
let result = true;
|
let result = true;
|
||||||
|
|
||||||
// строка соответствует фильтру, если сравнение всех значения из input
|
// строка соответствует фильтру, если сравнение всех значения из input
|
||||||
// со значением ячейки очередной строки - истина
|
// со значением ячейки очередной строки - истина
|
||||||
Object.entries(item).map(([key, val]) => {
|
Object.entries(item).map(([key, val]) => {
|
||||||
|
|
||||||
// текстовые поля проверяем на вхождение
|
// текстовые поля проверяем на вхождение
|
||||||
if (typeof val == 'string') {
|
if (typeof val == 'string') {
|
||||||
result &&= val.toLowerCase().includes(datafilter[correspond[key]])
|
result &&= val.toLowerCase().includes(datafilter[correspond[key]])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof val == 'number') {
|
|
||||||
result &&= datafilter[correspond[key]+"From"]<=Number(val)&& datafilter[correspond[key]+"To"]>=Number(val)
|
|
||||||
}
|
|
||||||
// САМОСТОЯТЕЛЬНО проверить числовые поля на принадлежность интервалу
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
// САМОСТОЯТЕЛЬНО проверить числовые поля на принадлежность интервалу
|
||||||
});
|
if (typeof val == 'number') {
|
||||||
|
result &&= datafilter[correspond[key][0]] <= Number(val) && datafilter[correspond[key][1]] >= Number(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
// САМОСТОЯТЕЛЬНО вызвать функцию, которая удаляет все строки таблицы с id=idTable
|
// САМОСТОЯТЕЛЬНО вызвать функцию, которая удаляет все строки таблицы с id=idTable
|
||||||
clearTable();
|
clearTable(idTable);
|
||||||
// показать на странице таблицу с отфильтрованными строками
|
// показать на странице таблицу с отфильтрованными строками
|
||||||
createTable(tableFilter, idTable);
|
createTable(tableFilter, idTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearFilters = (form) => {
|
||||||
|
form.querySelectorAll(`& input[type="text"],& input[type="number"]`).forEach((elem) => elem.value = null)
|
||||||
}
|
}
|
||||||
106
labs/lab1/JavaScript/main.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
doInit()
|
||||||
|
})
|
||||||
|
|
||||||
|
function doInit() {
|
||||||
|
createTable(buildings, 'list');
|
||||||
|
const clearFiltersButton = document.getElementById("clearFiltersButton");
|
||||||
|
clearFiltersButton.addEventListener("click", (event) => {
|
||||||
|
clearFilters(filter);
|
||||||
|
filterTable(buildings,'list',filter)
|
||||||
|
resetSort(sort);
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyFiltersButton = document.getElementById("applyFiltersButton");
|
||||||
|
applyFiltersButton.addEventListener("click", (event) => {
|
||||||
|
filterTable(buildings,'list',filter)
|
||||||
|
resetSort(sort);
|
||||||
|
})
|
||||||
|
|
||||||
|
setSortSelects(buildings[0], sort)
|
||||||
|
|
||||||
|
|
||||||
|
sort.fieldsFirst.addEventListener("change",()=>{
|
||||||
|
changeNextSelect(sort.fieldsFirst,sort.fieldsSecond.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.doSortButton.addEventListener("click", ()=>{if (!sortTable('list',sort)){
|
||||||
|
clearTable('list');
|
||||||
|
createTable(buildings, 'list');
|
||||||
|
}});
|
||||||
|
sort.resetSortButton.addEventListener("click", ()=>{resetSort(sort);
|
||||||
|
clearTable('list');
|
||||||
|
createTable(buildings, 'list');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// формирование полей элемента списка с заданным текстом и значением
|
||||||
|
|
||||||
|
const createOption = (str, val) => {
|
||||||
|
let item = document.createElement('option');
|
||||||
|
item.text = str;
|
||||||
|
item.value = val;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// формирование поля со списком
|
||||||
|
// параметры – массив со значениями элементов списка и элемент select
|
||||||
|
|
||||||
|
const setSortSelect = (arr, sortSelect) => {
|
||||||
|
|
||||||
|
// создаем OPTION Нет и добавляем ее в SELECT
|
||||||
|
sortSelect.append(createOption('Нет', 0));
|
||||||
|
// перебираем массив со значениями опций
|
||||||
|
arr.forEach((item, index) => {
|
||||||
|
// создаем OPTION из очередного ключа и добавляем в SELECT
|
||||||
|
// значение атрибута VALUE увеличиваем на 1, так как значение 0 имеет опция Нет
|
||||||
|
sortSelect.append(createOption(item, index + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// формируем поля со списком для многоуровневой сортировки
|
||||||
|
const setSortSelects = (data, dataForm) => {
|
||||||
|
|
||||||
|
// выделяем ключи словаря в массив
|
||||||
|
const head = Object.keys(data);
|
||||||
|
|
||||||
|
// находим все SELECT в форме
|
||||||
|
const allSelect = dataForm.getElementsByTagName('select');
|
||||||
|
|
||||||
|
for(const item of dataForm.elements){
|
||||||
|
// формируем очередной SELECT
|
||||||
|
setSortSelect(head, item);
|
||||||
|
|
||||||
|
// САМОСТОЯТЕЛЬНО все SELECT, кроме первого, сделать неизменяемым
|
||||||
|
}
|
||||||
|
let skipped = false
|
||||||
|
for(const elem of allSelect){
|
||||||
|
if(!skipped){
|
||||||
|
skipped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elem.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeNextSelect = (curSelect, nextSelectId) => {
|
||||||
|
|
||||||
|
let nextSelect = document.getElementById(nextSelectId);
|
||||||
|
|
||||||
|
nextSelect.disabled = false;
|
||||||
|
|
||||||
|
// в следующем SELECT выводим те же option, что и в текущем
|
||||||
|
nextSelect.innerHTML = curSelect.innerHTML;
|
||||||
|
|
||||||
|
// удаляем в следующем SELECT уже выбранную в текущем опцию
|
||||||
|
// если это не первая опция - отсутствие сортировки
|
||||||
|
if (curSelect.value != 0) {
|
||||||
|
nextSelect.remove(curSelect.value);
|
||||||
|
} else {
|
||||||
|
nextSelect.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSort = (form)=>{
|
||||||
|
form.querySelectorAll(`& select`).forEach((curSelect,index) => {if(index!=0){curSelect.disabled = true} curSelect.value = 0})
|
||||||
|
}
|
||||||
87
labs/lab1/JavaScript/sort.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*формируем массив для сортировки по двум уровням вида
|
||||||
|
[
|
||||||
|
{column: номер столбца, по которому осуществляется сортировка,
|
||||||
|
direction: порядок сортировки (true по убыванию, false по возрастанию)
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
const createSortArr = (data) => {
|
||||||
|
let sortArr = [];
|
||||||
|
|
||||||
|
const sortSelects = data.getElementsByTagName('select');
|
||||||
|
|
||||||
|
for (const item of sortSelects) {
|
||||||
|
// получаем номер выбранной опции
|
||||||
|
const keySort = item.value;
|
||||||
|
// в случае, если выбрана опция Нет, заканчиваем формировать массив
|
||||||
|
if (keySort == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// получаем порядок сортировки очередного уровня
|
||||||
|
// имя флажка сформировано как имя поля SELECT и слова Desc
|
||||||
|
const desc = document.getElementById(item.id + 'Desc').checked;
|
||||||
|
// очередной элемент массива - по какому столбцу и в каком порядке сортировать
|
||||||
|
sortArr.push(
|
||||||
|
{
|
||||||
|
column: keySort - 1,
|
||||||
|
direction: desc
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sortArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberColumns = [4, 5]
|
||||||
|
|
||||||
|
const sortTable = (idTable, formData) => {
|
||||||
|
|
||||||
|
// формируем управляющий массив для сортировки
|
||||||
|
const sortArr = createSortArr(formData);
|
||||||
|
|
||||||
|
// сортировать таблицу не нужно, во всех полях выбрана опция Нет
|
||||||
|
if (sortArr.length === 0) {
|
||||||
|
clearTable(idTable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
//находим нужную таблицу
|
||||||
|
let table = document.getElementById(idTable);
|
||||||
|
|
||||||
|
// преобразуем строки таблицы в массив
|
||||||
|
let rowData = Array.from(table.rows);
|
||||||
|
|
||||||
|
// удаляем элемент с заголовками таблицы
|
||||||
|
const headerRow = rowData.shift();
|
||||||
|
|
||||||
|
//сортируем данные по всем уровням сортировки
|
||||||
|
rowData.sort((first, second) => {
|
||||||
|
for (let { column, direction } of sortArr) {
|
||||||
|
const firstCell = first.cells[column].innerHTML;
|
||||||
|
const secondCell = second.cells[column].innerHTML;
|
||||||
|
|
||||||
|
// используем localeCompare для корректного сравнения
|
||||||
|
let comparison = null;
|
||||||
|
if (numberColumns.includes(column)) {
|
||||||
|
comparison = Number(firstCell) - Number(secondCell);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comparison = firstCell.localeCompare(secondCell);
|
||||||
|
}
|
||||||
|
// учитываем направление сортировки
|
||||||
|
if (comparison !== 0) {
|
||||||
|
return (direction ? -comparison : comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
clearTable(idTable)
|
||||||
|
//выводим отсортированную таблицу на страницу
|
||||||
|
|
||||||
|
let tbody = document.createElement('tbody');
|
||||||
|
rowData.forEach(item => {
|
||||||
|
tbody.append(item);
|
||||||
|
});
|
||||||
|
table.append(tbody);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
const createTable = (data, idTable) => {
|
const createTable = (data, idTable) => {
|
||||||
const table = document.getElementById(idTable);
|
const table = document.getElementById(idTable);
|
||||||
|
if (data.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const header = Object.keys(data[0]);
|
const header = Object.keys(data[0]);
|
||||||
|
|
||||||
/* создание шапки таблицы */
|
/* создание шапки таблицы */
|
||||||
const headerRow = createHeaderRow(header);
|
if (!table.firstElementChild) {
|
||||||
table.append(headerRow);
|
const headerRow = createHeaderRow(header);
|
||||||
|
table.append(headerRow);
|
||||||
|
}
|
||||||
|
|
||||||
/* создание тела таблицы */
|
/* создание тела таблицы */
|
||||||
const bodyRows = createBodyRows(data);
|
const bodyRows = createBodyRows(data);
|
||||||
table.append(bodyRows);
|
table.append(bodyRows);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearTable = (idTable) => {
|
const clearTable = (idTable) => {
|
||||||
const table = document.getElementById(idTable);
|
const table = document.getElementById(idTable);
|
||||||
table.removeChild(table.children[1]);
|
if (table.children.length > 1) {
|
||||||
|
table.removeChild(table.children[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createHeaderRow = (headers) => {
|
const createHeaderRow = (headers) => {
|
||||||
@@ -23,12 +30,14 @@ const createHeaderRow = (headers) => {
|
|||||||
th.innerHTML = header;
|
th.innerHTML = header;
|
||||||
tr.append(th);
|
tr.append(th);
|
||||||
});
|
});
|
||||||
return tr;
|
const thead = document.createElement('thead')
|
||||||
|
thead.appendChild(tr)
|
||||||
|
return thead;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createBodyRows = (rows) =>{
|
const createBodyRows = (rows) => {
|
||||||
const body = document.createElement('tbody');
|
const body = document.createElement('tbody');
|
||||||
rows.forEach(row =>{
|
rows.forEach(row => {
|
||||||
const rowElement = document.createElement('tr');
|
const rowElement = document.createElement('tr');
|
||||||
for (let key in row) {
|
for (let key in row) {
|
||||||
const td = document.createElement('td');
|
const td = document.createElement('td');
|
||||||
76
labs/lab1/index.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Фильтры и сортировка</title>
|
||||||
|
<link rel="stylesheet" href="CSS/style.css">
|
||||||
|
<script src="JavaScript/data.js"></script>
|
||||||
|
<script src="JavaScript/main.js"></script>
|
||||||
|
<script src="JavaScript/table.js"></script>
|
||||||
|
<script src="JavaScript/filter.js"></script>
|
||||||
|
<script src="JavaScript/sort.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h3>Фильтр</h3>
|
||||||
|
<form id="filter" name="filter">
|
||||||
|
<p>
|
||||||
|
<label for="structure">Название:</label>
|
||||||
|
<input type="text" id="structure">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="category">Тип:</label>
|
||||||
|
<input type="text" id="category">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="country">Страна:</label>
|
||||||
|
<input type="text" id="country">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="city">Город:</label>
|
||||||
|
<input type="text" id="city">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="yearFrom">Год: </label>
|
||||||
|
от <input type="number" id="yearFrom">
|
||||||
|
до <input type="number" id="yearTo">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="heightFrom">Высота:</label>
|
||||||
|
от <input type="number" id="heightFrom">
|
||||||
|
до <input type="number" id="heightTo">
|
||||||
|
</p>
|
||||||
|
<input id="applyFiltersButton" type="button" value="Найти">
|
||||||
|
<input id="clearFiltersButton" type="button" value="Очистить фильтры">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Сортировка</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<form id="sort">
|
||||||
|
<p> Сортировать по</p>
|
||||||
|
<p>
|
||||||
|
<select id="fieldsFirst">
|
||||||
|
</select>
|
||||||
|
по убыванию? <input type="checkbox" id="fieldsFirstDesc">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<select id="fieldsSecond">
|
||||||
|
</select>
|
||||||
|
по убыванию? <input type="checkbox" id="fieldsSecondDesc">
|
||||||
|
</p>
|
||||||
|
<input type="button" value="Сортировать" id="doSortButton">
|
||||||
|
<input type="button" value="Сбросить сортировку" id="resetSortButton">
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="list">
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |